Upgrade to ExtJS 4.0.0 - Released 04/26/2011
[extjs.git] / ext-all-debug-w-comments.js
1 /*
2 Ext JS - JavaScript Library
3 Copyright (c) 2006-2011, Sencha Inc.
4 All rights reserved.
5 licensing@sencha.com
6 */
7 /**
8  * @class Ext
9  * @singleton
10  */
11 (function() {
12     var global = this,
13         objectPrototype = Object.prototype,
14         toString = Object.prototype.toString,
15         enumerables = true,
16         enumerablesTest = { toString: 1 },
17         i;
18
19     if (typeof Ext === 'undefined') {
20         global.Ext = {};
21     }
22
23     Ext.global = global;
24
25     for (i in enumerablesTest) {
26         enumerables = null;
27     }
28
29     if (enumerables) {
30         enumerables = ['hasOwnProperty', 'valueOf', 'isPrototypeOf', 'propertyIsEnumerable',
31                        'toLocaleString', 'toString', 'constructor'];
32     }
33
34     /**
35      * An array containing extra enumerables for old browsers
36      * @type Array
37      */
38     Ext.enumerables = enumerables;
39
40     /**
41      * Copies all the properties of config to the specified object.
42      * Note that if recursive merging and cloning without referencing the original objects / arrays is needed, use
43      * {@link Ext.Object#merge} instead.
44      * @param {Object} object The receiver of the properties
45      * @param {Object} config The source of the properties
46      * @param {Object} defaults A different object that will also be applied for default values
47      * @return {Object} returns obj
48      */
49     Ext.apply = function(object, config, defaults) {
50         if (defaults) {
51             Ext.apply(object, defaults);
52         }
53
54         if (object && config && typeof config === 'object') {
55             var i, j, k;
56
57             for (i in config) {
58                 object[i] = config[i];
59             }
60
61             if (enumerables) {
62                 for (j = enumerables.length; j--;) {
63                     k = enumerables[j];
64                     if (config.hasOwnProperty(k)) {
65                         object[k] = config[k];
66                     }
67                 }
68             }
69         }
70
71         return object;
72     };
73
74     Ext.buildSettings = Ext.apply({
75         baseCSSPrefix: 'x-',
76         scopeResetCSS: false
77     }, Ext.buildSettings || {});
78
79     Ext.apply(Ext, {
80         /**
81          * A reusable empty function
82          */
83         emptyFn: function() {},
84
85         baseCSSPrefix: Ext.buildSettings.baseCSSPrefix,
86
87         /**
88          * Copies all the properties of config to object if they don't already exist.
89          * @function
90          * @param {Object} object The receiver of the properties
91          * @param {Object} config The source of the properties
92          * @return {Object} returns obj
93          */
94         applyIf: function(object, config) {
95             var property;
96
97             if (object) {
98                 for (property in config) {
99                     if (object[property] === undefined) {
100                         object[property] = config[property];
101                     }
102                 }
103             }
104
105             return object;
106         },
107
108         /**
109          * Iterates either an array or an object. This method delegates to
110          * {@link Ext.Array#each Ext.Array.each} if the given value is iterable, and {@link Ext.Object#each Ext.Object.each} otherwise.
111          *
112          * @param {Object/Array} object The object or array to be iterated.
113          * @param {Function} fn The function to be called for each iteration. See and {@link Ext.Array#each Ext.Array.each} and
114          * {@link Ext.Object#each Ext.Object.each} for detailed lists of arguments passed to this function depending on the given object
115          * type that is being iterated.
116          * @param {Object} scope (Optional) The scope (`this` reference) in which the specified function is executed.
117          * Defaults to the object being iterated itself.
118          * @markdown
119          */
120         iterate: function(object, fn, scope) {
121             if (Ext.isEmpty(object)) {
122                 return;
123             }
124
125             if (scope === undefined) {
126                 scope = object;
127             }
128
129             if (Ext.isIterable(object)) {
130                 Ext.Array.each.call(Ext.Array, object, fn, scope);
131             }
132             else {
133                 Ext.Object.each.call(Ext.Object, object, fn, scope);
134             }
135         }
136     });
137
138     Ext.apply(Ext, {
139
140         /**
141          * This method deprecated. Use {@link Ext#define Ext.define} instead.
142          * @function
143          * @param {Function} superclass
144          * @param {Object} overrides
145          * @return {Function} The subclass constructor from the <tt>overrides</tt> parameter, or a generated one if not provided.
146          * @deprecated 4.0.0 Use {@link Ext#define Ext.define} instead
147          */
148         extend: function() {
149             // inline overrides
150             var objectConstructor = objectPrototype.constructor,
151                 inlineOverrides = function(o) {
152                 for (var m in o) {
153                     if (!o.hasOwnProperty(m)) {
154                         continue;
155                     }
156                     this[m] = o[m];
157                 }
158             };
159
160             return function(subclass, superclass, overrides) {
161                 // First we check if the user passed in just the superClass with overrides
162                 if (Ext.isObject(superclass)) {
163                     overrides = superclass;
164                     superclass = subclass;
165                     subclass = overrides.constructor !== objectConstructor ? overrides.constructor : function() {
166                         superclass.apply(this, arguments);
167                     };
168                 }
169
170                 if (!superclass) {
171                     Ext.Error.raise({
172                         sourceClass: 'Ext',
173                         sourceMethod: 'extend',
174                         msg: 'Attempting to extend from a class which has not been loaded on the page.'
175                     });
176                 }
177
178                 // We create a new temporary class
179                 var F = function() {},
180                     subclassProto, superclassProto = superclass.prototype;
181
182                 F.prototype = superclassProto;
183                 subclassProto = subclass.prototype = new F();
184                 subclassProto.constructor = subclass;
185                 subclass.superclass = superclassProto;
186
187                 if (superclassProto.constructor === objectConstructor) {
188                     superclassProto.constructor = superclass;
189                 }
190
191                 subclass.override = function(overrides) {
192                     Ext.override(subclass, overrides);
193                 };
194
195                 subclassProto.override = inlineOverrides;
196                 subclassProto.proto = subclassProto;
197
198                 subclass.override(overrides);
199                 subclass.extend = function(o) {
200                     return Ext.extend(subclass, o);
201                 };
202
203                 return subclass;
204             };
205         }(),
206
207         /**
208          * Proxy to {@link Ext.Base#override}. Please refer {@link Ext.Base#override} for further details.
209
210     Ext.define('My.cool.Class', {
211         sayHi: function() {
212             alert('Hi!');
213         }
214     }
215
216     Ext.override(My.cool.Class, {
217         sayHi: function() {
218             alert('About to say...');
219
220             this.callOverridden();
221         }
222     });
223
224     var cool = new My.cool.Class();
225     cool.sayHi(); // alerts 'About to say...'
226                   // alerts 'Hi!'
227
228          * Please note that `this.callOverridden()` only works if the class was previously
229          * created with {@link Ext#define)
230          *
231          * @param {Object} cls The class to override
232          * @param {Object} overrides The list of functions to add to origClass. This should be specified as an object literal
233          * containing one or more methods.
234          * @method override
235          * @markdown
236          */
237         override: function(cls, overrides) {
238             if (cls.prototype.$className) {
239                 return cls.override(overrides);
240             }
241             else {
242                 Ext.apply(cls.prototype, overrides);
243             }
244         }
245     });
246
247     // A full set of static methods to do type checking
248     Ext.apply(Ext, {
249
250         /**
251          * Returns the given value itself if it's not empty, as described in {@link Ext#isEmpty}; returns the default
252          * value (second argument) otherwise.
253          *
254          * @param {Mixed} value The value to test
255          * @param {Mixed} defaultValue The value to return if the original value is empty
256          * @param {Boolean} allowBlank (optional) true to allow zero length strings to qualify as non-empty (defaults to false)
257          * @return {Mixed} value, if non-empty, else defaultValue
258          */
259         valueFrom: function(value, defaultValue, allowBlank){
260             return Ext.isEmpty(value, allowBlank) ? defaultValue : value;
261         },
262
263         /**
264          * Returns the type of the given variable in string format. List of possible values are:
265          *
266          * - `undefined`: If the given value is `undefined`
267          * - `null`: If the given value is `null`
268          * - `string`: If the given value is a string
269          * - `number`: If the given value is a number
270          * - `boolean`: If the given value is a boolean value
271          * - `date`: If the given value is a `Date` object
272          * - `function`: If the given value is a function reference
273          * - `object`: If the given value is an object
274          * - `array`: If the given value is an array
275          * - `regexp`: If the given value is a regular expression
276          * - `element`: If the given value is a DOM Element
277          * - `textnode`: If the given value is a DOM text node and contains something other than whitespace
278          * - `whitespace`: If the given value is a DOM text node and contains only whitespace
279          *
280          * @param {Mixed} value
281          * @return {String}
282          * @markdown
283          */
284         typeOf: function(value) {
285             if (value === null) {
286                 return 'null';
287             }
288
289             var type = typeof value;
290
291             if (type === 'undefined' || type === 'string' || type === 'number' || type === 'boolean') {
292                 return type;
293             }
294
295             var typeToString = toString.call(value);
296
297             switch(typeToString) {
298                 case '[object Array]':
299                     return 'array';
300                 case '[object Date]':
301                     return 'date';
302                 case '[object Boolean]':
303                     return 'boolean';
304                 case '[object Number]':
305                     return 'number';
306                 case '[object RegExp]':
307                     return 'regexp';
308             }
309
310             if (type === 'function') {
311                 return 'function';
312             }
313
314             if (type === 'object') {
315                 if (value.nodeType !== undefined) {
316                     if (value.nodeType === 3) {
317                         return (/\S/).test(value.nodeValue) ? 'textnode' : 'whitespace';
318                     }
319                     else {
320                         return 'element';
321                     }
322                 }
323
324                 return 'object';
325             }
326
327             Ext.Error.raise({
328                 sourceClass: 'Ext',
329                 sourceMethod: 'typeOf',
330                 msg: 'Failed to determine the type of the specified value "' + value + '". This is most likely a bug.'
331             });
332         },
333
334         /**
335          * Returns true if the passed value is empty, false otherwise. The value is deemed to be empty if it is either:
336          *
337          * - `null`
338          * - `undefined`
339          * - a zero-length array
340          * - a zero-length string (Unless the `allowEmptyString` parameter is set to `true`)
341          *
342          * @param {Mixed} value The value to test
343          * @param {Boolean} allowEmptyString (optional) true to allow empty strings (defaults to false)
344          * @return {Boolean}
345          * @markdown
346          */
347         isEmpty: function(value, allowEmptyString) {
348             return (value === null) || (value === undefined) || (!allowEmptyString ? value === '' : false) || (Ext.isArray(value) && value.length === 0);
349         },
350
351         /**
352          * Returns true if the passed value is a JavaScript Array, false otherwise.
353          *
354          * @param {Mixed} target The target to test
355          * @return {Boolean}
356          */
357         isArray: ('isArray' in Array) ? Array.isArray : function(value) {
358             return toString.call(value) === '[object Array]';
359         },
360
361         /**
362          * Returns true if the passed value is a JavaScript Date object, false otherwise.
363          * @param {Object} object The object to test
364          * @return {Boolean}
365          */
366         isDate: function(value) {
367             return toString.call(value) === '[object Date]';
368         },
369
370         /**
371          * Returns true if the passed value is a JavaScript Object, false otherwise.
372          * @param {Mixed} value The value to test
373          * @return {Boolean}
374          */
375         isObject: (toString.call(null) === '[object Object]') ?
376         function(value) {
377             return value !== null && value !== undefined && toString.call(value) === '[object Object]' && value.nodeType === undefined;
378         } :
379         function(value) {
380             return toString.call(value) === '[object Object]';
381         },
382
383         /**
384          * Returns true if the passed value is a JavaScript 'primitive', a string, number or boolean.
385          * @param {Mixed} value The value to test
386          * @return {Boolean}
387          */
388         isPrimitive: function(value) {
389             var type = typeof value;
390
391             return type === 'string' || type === 'number' || type === 'boolean';
392         },
393
394         /**
395          * Returns true if the passed value is a JavaScript Function, false otherwise.
396          * @param {Mixed} value The value to test
397          * @return {Boolean}
398          */
399         isFunction:
400         // Safari 3.x and 4.x returns 'function' for typeof <NodeList>, hence we need to fall back to using
401         // Object.prorotype.toString (slower)
402         (typeof document !== 'undefined' && typeof document.getElementsByTagName('body') === 'function') ? function(value) {
403             return toString.call(value) === '[object Function]';
404         } : function(value) {
405             return typeof value === 'function';
406         },
407
408         /**
409          * Returns true if the passed value is a number. Returns false for non-finite numbers.
410          * @param {Mixed} value The value to test
411          * @return {Boolean}
412          */
413         isNumber: function(value) {
414             return typeof value === 'number' && isFinite(value);
415         },
416
417         /**
418          * Validates that a value is numeric.
419          * @param {Mixed} value Examples: 1, '1', '2.34'
420          * @return {Boolean} True if numeric, false otherwise
421          */
422         isNumeric: function(value) {
423             return !isNaN(parseFloat(value)) && isFinite(value);
424         },
425
426         /**
427          * Returns true if the passed value is a string.
428          * @param {Mixed} value The value to test
429          * @return {Boolean}
430          */
431         isString: function(value) {
432             return typeof value === 'string';
433         },
434
435         /**
436          * Returns true if the passed value is a boolean.
437          *
438          * @param {Mixed} value The value to test
439          * @return {Boolean}
440          */
441         isBoolean: function(value) {
442             return typeof value === 'boolean';
443         },
444
445         /**
446          * Returns true if the passed value is an HTMLElement
447          * @param {Mixed} value The value to test
448          * @return {Boolean}
449          */
450         isElement: function(value) {
451             return value ? value.nodeType !== undefined : false;
452         },
453
454         /**
455          * Returns true if the passed value is a TextNode
456          * @param {Mixed} value The value to test
457          * @return {Boolean}
458          */
459         isTextNode: function(value) {
460             return value ? value.nodeName === "#text" : false;
461         },
462
463         /**
464          * Returns true if the passed value is defined.
465          * @param {Mixed} value The value to test
466          * @return {Boolean}
467          */
468         isDefined: function(value) {
469             return typeof value !== 'undefined';
470         },
471
472         /**
473          * Returns true if the passed value is iterable, false otherwise
474          * @param {Mixed} value The value to test
475          * @return {Boolean}
476          */
477         isIterable: function(value) {
478             return (value && typeof value !== 'string') ? value.length !== undefined : false;
479         }
480     });
481
482     Ext.apply(Ext, {
483
484         /**
485          * Clone almost any type of variable including array, object, DOM nodes and Date without keeping the old reference
486          * @param {Mixed} item The variable to clone
487          * @return {Mixed} clone
488          */
489         clone: function(item) {
490             if (item === null || item === undefined) {
491                 return item;
492             }
493
494             // DOM nodes
495             // TODO proxy this to Ext.Element.clone to handle automatic id attribute changing
496             // recursively
497             if (item.nodeType && item.cloneNode) {
498                 return item.cloneNode(true);
499             }
500
501             var type = toString.call(item);
502
503             // Date
504             if (type === '[object Date]') {
505                 return new Date(item.getTime());
506             }
507
508             var i, j, k, clone, key;
509
510             // Array
511             if (type === '[object Array]') {
512                 i = item.length;
513
514                 clone = [];
515
516                 while (i--) {
517                     clone[i] = Ext.clone(item[i]);
518                 }
519             }
520             // Object
521             else if (type === '[object Object]' && item.constructor === Object) {
522                 clone = {};
523
524                 for (key in item) {
525                     clone[key] = Ext.clone(item[key]);
526                 }
527
528                 if (enumerables) {
529                     for (j = enumerables.length; j--;) {
530                         k = enumerables[j];
531                         clone[k] = item[k];
532                     }
533                 }
534             }
535
536             return clone || item;
537         },
538
539         /**
540          * @private
541          * Generate a unique reference of Ext in the global scope, useful for sandboxing
542          */
543         getUniqueGlobalNamespace: function() {
544             var uniqueGlobalNamespace = this.uniqueGlobalNamespace;
545
546             if (uniqueGlobalNamespace === undefined) {
547                 var i = 0;
548
549                 do {
550                     uniqueGlobalNamespace = 'ExtSandbox' + (++i);
551                 } while (Ext.global[uniqueGlobalNamespace] !== undefined);
552
553                 Ext.global[uniqueGlobalNamespace] = Ext;
554                 this.uniqueGlobalNamespace = uniqueGlobalNamespace;
555             }
556
557             return uniqueGlobalNamespace;
558         },
559
560         /**
561          * @private
562          */
563         functionFactory: function() {
564             var args = Array.prototype.slice.call(arguments);
565
566             if (args.length > 0) {
567                 args[args.length - 1] = 'var Ext=window.' + this.getUniqueGlobalNamespace() + ';' +
568                     args[args.length - 1];
569             }
570
571             return Function.prototype.constructor.apply(Function.prototype, args);
572         }
573     });
574
575     /**
576      * Old alias to {@link Ext#typeOf}
577      * @deprecated 4.0.0 Use {@link Ext#typeOf} instead
578      */
579     Ext.type = Ext.typeOf;
580
581 })();
582
583 /**
584  * @author Jacky Nguyen <jacky@sencha.com>
585  * @docauthor Jacky Nguyen <jacky@sencha.com>
586  * @class Ext.Version
587  *
588  * A utility class that wrap around a string version number and provide convenient
589  * method to perform comparison. See also: {@link Ext.Version#compare compare}. Example:
590
591     var version = new Ext.Version('1.0.2beta');
592     console.log("Version is " + version); // Version is 1.0.2beta
593
594     console.log(version.getMajor()); // 1
595     console.log(version.getMinor()); // 0
596     console.log(version.getPatch()); // 2
597     console.log(version.getBuild()); // 0
598     console.log(version.getRelease()); // beta
599
600     console.log(version.isGreaterThan('1.0.1')); // True
601     console.log(version.isGreaterThan('1.0.2alpha')); // True
602     console.log(version.isGreaterThan('1.0.2RC')); // False
603     console.log(version.isGreaterThan('1.0.2')); // False
604     console.log(version.isLessThan('1.0.2')); // True
605
606     console.log(version.match(1.0)); // True
607     console.log(version.match('1.0.2')); // True
608
609  * @markdown
610  */
611 (function() {
612
613 // Current core version
614 var version = '4.0.0', Version;
615     Ext.Version = Version = Ext.extend(Object, {
616
617         /**
618          * @constructor
619          * @param {String/Number} version The version number in the follow standard format: major[.minor[.patch[.build[release]]]]
620          * Examples: 1.0 or 1.2.3beta or 1.2.3.4RC
621          * @return {Ext.Version} this
622          * @param version
623          */
624         constructor: function(version) {
625             var parts, releaseStartIndex;
626
627             if (version instanceof Version) {
628                 return version;
629             }
630
631             this.version = this.shortVersion = String(version).toLowerCase().replace(/_/g, '.').replace(/[\-+]/g, '');
632
633             releaseStartIndex = this.version.search(/([^\d\.])/);
634
635             if (releaseStartIndex !== -1) {
636                 this.release = this.version.substr(releaseStartIndex, version.length);
637                 this.shortVersion = this.version.substr(0, releaseStartIndex);
638             }
639
640             this.shortVersion = this.shortVersion.replace(/[^\d]/g, '');
641
642             parts = this.version.split('.');
643
644             this.major = parseInt(parts.shift() || 0, 10);
645             this.minor = parseInt(parts.shift() || 0, 10);
646             this.patch = parseInt(parts.shift() || 0, 10);
647             this.build = parseInt(parts.shift() || 0, 10);
648
649             return this;
650         },
651
652         /**
653          * Override the native toString method
654          * @private
655          * @return {String} version
656          */
657         toString: function() {
658             return this.version;
659         },
660
661         /**
662          * Override the native valueOf method
663          * @private
664          * @return {String} version
665          */
666         valueOf: function() {
667             return this.version;
668         },
669
670         /**
671          * Returns the major component value
672          * @return {Number} major
673          */
674         getMajor: function() {
675             return this.major || 0;
676         },
677
678         /**
679          * Returns the minor component value
680          * @return {Number} minor
681          */
682         getMinor: function() {
683             return this.minor || 0;
684         },
685
686         /**
687          * Returns the patch component value
688          * @return {Number} patch
689          */
690         getPatch: function() {
691             return this.patch || 0;
692         },
693
694         /**
695          * Returns the build component value
696          * @return {Number} build
697          */
698         getBuild: function() {
699             return this.build || 0;
700         },
701
702         /**
703          * Returns the release component value
704          * @return {Number} release
705          */
706         getRelease: function() {
707             return this.release || '';
708         },
709
710         /**
711          * Returns whether this version if greater than the supplied argument
712          * @param {String/Number} target The version to compare with
713          * @return {Boolean} True if this version if greater than the target, false otherwise
714          */
715         isGreaterThan: function(target) {
716             return Version.compare(this.version, target) === 1;
717         },
718
719         /**
720          * Returns whether this version if smaller than the supplied argument
721          * @param {String/Number} target The version to compare with
722          * @return {Boolean} True if this version if smaller than the target, false otherwise
723          */
724         isLessThan: function(target) {
725             return Version.compare(this.version, target) === -1;
726         },
727
728         /**
729          * Returns whether this version equals to the supplied argument
730          * @param {String/Number} target The version to compare with
731          * @return {Boolean} True if this version equals to the target, false otherwise
732          */
733         equals: function(target) {
734             return Version.compare(this.version, target) === 0;
735         },
736
737         /**
738          * Returns whether this version matches the supplied argument. Example:
739          * <pre><code>
740          * var version = new Ext.Version('1.0.2beta');
741          * console.log(version.match(1)); // True
742          * console.log(version.match(1.0)); // True
743          * console.log(version.match('1.0.2')); // True
744          * console.log(version.match('1.0.2RC')); // False
745          * </code></pre>
746          * @param {String/Number} target The version to compare with
747          * @return {Boolean} True if this version matches the target, false otherwise
748          */
749         match: function(target) {
750             target = String(target);
751             return this.version.substr(0, target.length) === target;
752         },
753
754         /**
755          * Returns this format: [major, minor, patch, build, release]. Useful for comparison
756          * @return {Array}
757          */
758         toArray: function() {
759             return [this.getMajor(), this.getMinor(), this.getPatch(), this.getBuild(), this.getRelease()];
760         },
761
762         /**
763          * Returns shortVersion version without dots and release
764          * @return {String}
765          */
766         getShortVersion: function() {
767             return this.shortVersion;
768         }
769     });
770
771     Ext.apply(Version, {
772         // @private
773         releaseValueMap: {
774             'dev': -6,
775             'alpha': -5,
776             'a': -5,
777             'beta': -4,
778             'b': -4,
779             'rc': -3,
780             '#': -2,
781             'p': -1,
782             'pl': -1
783         },
784
785         /**
786          * Converts a version component to a comparable value
787          *
788          * @static
789          * @param {Mixed} value The value to convert
790          * @return {Mixed}
791          */
792         getComponentValue: function(value) {
793             return !value ? 0 : (isNaN(value) ? this.releaseValueMap[value] || value : parseInt(value, 10));
794         },
795
796         /**
797          * Compare 2 specified versions, starting from left to right. If a part contains special version strings,
798          * they are handled in the following order:
799          * 'dev' < 'alpha' = 'a' < 'beta' = 'b' < 'RC' = 'rc' < '#' < 'pl' = 'p' < 'anything else'
800          *
801          * @static
802          * @param {String} current The current version to compare to
803          * @param {String} target The target version to compare to
804          * @return {Number} Returns -1 if the current version is smaller than the target version, 1 if greater, and 0 if they're equivalent
805          */
806         compare: function(current, target) {
807             var currentValue, targetValue, i;
808
809             current = new Version(current).toArray();
810             target = new Version(target).toArray();
811
812             for (i = 0; i < Math.max(current.length, target.length); i++) {
813                 currentValue = this.getComponentValue(current[i]);
814                 targetValue = this.getComponentValue(target[i]);
815
816                 if (currentValue < targetValue) {
817                     return -1;
818                 } else if (currentValue > targetValue) {
819                     return 1;
820                 }
821             }
822
823             return 0;
824         }
825     });
826
827     Ext.apply(Ext, {
828         /**
829          * @private
830          */
831         versions: {},
832
833         /**
834          * @private
835          */
836         lastRegisteredVersion: null,
837
838         /**
839          * Set version number for the given package name.
840          *
841          * @param {String} packageName The package name, for example: 'core', 'touch', 'extjs'
842          * @param {String/Ext.Version} version The version, for example: '1.2.3alpha', '2.4.0-dev'
843          * @return {Ext}
844          */
845         setVersion: function(packageName, version) {
846             Ext.versions[packageName] = new Version(version);
847             Ext.lastRegisteredVersion = Ext.versions[packageName];
848
849             return this;
850         },
851
852         /**
853          * Get the version number of the supplied package name; will return the last registered version
854          * (last Ext.setVersion call) if there's no package name given.
855          *
856          * @param {String} packageName (Optional) The package name, for example: 'core', 'touch', 'extjs'
857          * @return {Ext.Version} The version
858          */
859         getVersion: function(packageName) {
860             if (packageName === undefined) {
861                 return Ext.lastRegisteredVersion;
862             }
863
864             return Ext.versions[packageName];
865         },
866
867         /**
868          * Create a closure for deprecated code.
869          *
870     // This means Ext.oldMethod is only supported in 4.0.0beta and older.
871     // If Ext.getVersion('extjs') returns a version that is later than '4.0.0beta', for example '4.0.0RC',
872     // the closure will not be invoked
873     Ext.deprecate('extjs', '4.0.0beta', function() {
874         Ext.oldMethod = Ext.newMethod;
875
876         ...
877     });
878
879          * @param {String} packageName The package name
880          * @param {String} since The last version before it's deprecated
881          * @param {Function} closure The callback function to be executed with the specified version is less than the current version
882          * @param {Object} scope The execution scope (<tt>this</tt>) if the closure
883          * @markdown
884          */
885         deprecate: function(packageName, since, closure, scope) {
886             if (Version.compare(Ext.getVersion(packageName), since) < 1) {
887                 closure.call(scope);
888             }
889         }
890     }); // End Versioning
891
892     Ext.setVersion('core', version);
893
894 })();
895
896 /**
897  * @class Ext.String
898  *
899  * A collection of useful static methods to deal with strings
900  * @singleton
901  */
902
903 Ext.String = {
904     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,
905     escapeRe: /('|\\)/g,
906     formatRe: /\{(\d+)\}/g,
907     escapeRegexRe: /([-.*+?^${}()|[\]\/\\])/g,
908
909     /**
910      * Convert certain characters (&, <, >, and ') to their HTML character equivalents for literal display in web pages.
911      * @param {String} value The string to encode
912      * @return {String} The encoded text
913      */
914     htmlEncode: (function() {
915         var entities = {
916             '&': '&amp;',
917             '>': '&gt;',
918             '<': '&lt;',
919             '"': '&quot;'
920         }, keys = [], p, regex;
921         
922         for (p in entities) {
923             keys.push(p);
924         }
925         
926         regex = new RegExp('(' + keys.join('|') + ')', 'g');
927         
928         return function(value) {
929             return (!value) ? value : String(value).replace(regex, function(match, capture) {
930                 return entities[capture];    
931             });
932         };
933     })(),
934
935     /**
936      * Convert certain characters (&, <, >, and ') from their HTML character equivalents.
937      * @param {String} value The string to decode
938      * @return {String} The decoded text
939      */
940     htmlDecode: (function() {
941         var entities = {
942             '&amp;': '&',
943             '&gt;': '>',
944             '&lt;': '<',
945             '&quot;': '"'
946         }, keys = [], p, regex;
947         
948         for (p in entities) {
949             keys.push(p);
950         }
951         
952         regex = new RegExp('(' + keys.join('|') + '|&#[0-9]{1,5};' + ')', 'g');
953         
954         return function(value) {
955             return (!value) ? value : String(value).replace(regex, function(match, capture) {
956                 if (capture in entities) {
957                     return entities[capture];
958                 } else {
959                     return String.fromCharCode(parseInt(capture.substr(2), 10));
960                 }
961             });
962         };
963     })(),
964
965     /**
966      * Appends content to the query string of a URL, handling logic for whether to place
967      * a question mark or ampersand.
968      * @param {String} url The URL to append to.
969      * @param {String} string The content to append to the URL.
970      * @return (String) The resulting URL
971      */
972     urlAppend : function(url, string) {
973         if (!Ext.isEmpty(string)) {
974             return url + (url.indexOf('?') === -1 ? '?' : '&') + string;
975         }
976
977         return url;
978     },
979
980     /**
981      * Trims whitespace from either end of a string, leaving spaces within the string intact.  Example:
982      * @example
983 var s = '  foo bar  ';
984 alert('-' + s + '-');         //alerts "- foo bar -"
985 alert('-' + Ext.String.trim(s) + '-');  //alerts "-foo bar-"
986
987      * @param {String} string The string to escape
988      * @return {String} The trimmed string
989      */
990     trim: function(string) {
991         return string.replace(Ext.String.trimRegex, "");
992     },
993
994     /**
995      * Capitalize the given string
996      * @param {String} string
997      * @return {String}
998      */
999     capitalize: function(string) {
1000         return string.charAt(0).toUpperCase() + string.substr(1);
1001     },
1002
1003     /**
1004      * Truncate a string and add an ellipsis ('...') to the end if it exceeds the specified length
1005      * @param {String} value The string to truncate
1006      * @param {Number} length The maximum length to allow before truncating
1007      * @param {Boolean} word True to try to find a common word break
1008      * @return {String} The converted text
1009      */
1010     ellipsis: function(value, len, word) {
1011         if (value && value.length > len) {
1012             if (word) {
1013                 var vs = value.substr(0, len - 2),
1014                 index = Math.max(vs.lastIndexOf(' '), vs.lastIndexOf('.'), vs.lastIndexOf('!'), vs.lastIndexOf('?'));
1015                 if (index !== -1 && index >= (len - 15)) {
1016                     return vs.substr(0, index) + "...";
1017                 }
1018             }
1019             return value.substr(0, len - 3) + "...";
1020         }
1021         return value;
1022     },
1023
1024     /**
1025      * Escapes the passed string for use in a regular expression
1026      * @param {String} string
1027      * @return {String}
1028      */
1029     escapeRegex: function(string) {
1030         return string.replace(Ext.String.escapeRegexRe, "\\$1");
1031     },
1032
1033     /**
1034      * Escapes the passed string for ' and \
1035      * @param {String} string The string to escape
1036      * @return {String} The escaped string
1037      */
1038     escape: function(string) {
1039         return string.replace(Ext.String.escapeRe, "\\$1");
1040     },
1041
1042     /**
1043      * Utility function that allows you to easily switch a string between two alternating values.  The passed value
1044      * is compared to the current string, and if they are equal, the other value that was passed in is returned.  If
1045      * they are already different, the first value passed in is returned.  Note that this method returns the new value
1046      * but does not change the current string.
1047      * <pre><code>
1048     // alternate sort directions
1049     sort = Ext.String.toggle(sort, 'ASC', 'DESC');
1050
1051     // instead of conditional logic:
1052     sort = (sort == 'ASC' ? 'DESC' : 'ASC');
1053        </code></pre>
1054      * @param {String} string The current string
1055      * @param {String} value The value to compare to the current string
1056      * @param {String} other The new value to use if the string already equals the first value passed in
1057      * @return {String} The new value
1058      */
1059     toggle: function(string, value, other) {
1060         return string === value ? other : value;
1061     },
1062
1063     /**
1064      * Pads the left side of a string with a specified character.  This is especially useful
1065      * for normalizing number and date strings.  Example usage:
1066      *
1067      * <pre><code>
1068 var s = Ext.String.leftPad('123', 5, '0');
1069 // s now contains the string: '00123'
1070        </code></pre>
1071      * @param {String} string The original string
1072      * @param {Number} size The total length of the output string
1073      * @param {String} character (optional) The character with which to pad the original string (defaults to empty string " ")
1074      * @return {String} The padded string
1075      */
1076     leftPad: function(string, size, character) {
1077         var result = String(string);
1078         character = character || " ";
1079         while (result.length < size) {
1080             result = character + result;
1081         }
1082         return result;
1083     },
1084
1085     /**
1086      * Allows you to define a tokenized string and pass an arbitrary number of arguments to replace the tokens.  Each
1087      * token must be unique, and must increment in the format {0}, {1}, etc.  Example usage:
1088      * <pre><code>
1089 var cls = 'my-class', text = 'Some text';
1090 var s = Ext.String.format('&lt;div class="{0}">{1}&lt;/div>', cls, text);
1091 // s now contains the string: '&lt;div class="my-class">Some text&lt;/div>'
1092        </code></pre>
1093      * @param {String} string The tokenized string to be formatted
1094      * @param {String} value1 The value to replace token {0}
1095      * @param {String} value2 Etc...
1096      * @return {String} The formatted string
1097      */
1098     format: function(format) {
1099         var args = Ext.Array.toArray(arguments, 1);
1100         return format.replace(Ext.String.formatRe, function(m, i) {
1101             return args[i];
1102         });
1103     }
1104 };
1105
1106 /**
1107  * @class Ext.Number
1108  *
1109  * A collection of useful static methods to deal with numbers
1110  * @singleton
1111  */
1112
1113 (function() {
1114
1115 var isToFixedBroken = (0.9).toFixed() !== '1';
1116
1117 Ext.Number = {
1118     /**
1119      * Checks whether or not the current number is within a desired range.  If the number is already within the
1120      * range it is returned, otherwise the min or max value is returned depending on which side of the range is
1121      * exceeded. Note that this method returns the constrained value but does not change the current number.
1122      * @param {Number} number The number to check
1123      * @param {Number} min The minimum number in the range
1124      * @param {Number} max The maximum number in the range
1125      * @return {Number} The constrained value if outside the range, otherwise the current value
1126      */
1127     constrain: function(number, min, max) {
1128         number = parseFloat(number);
1129
1130         if (!isNaN(min)) {
1131             number = Math.max(number, min);
1132         }
1133         if (!isNaN(max)) {
1134             number = Math.min(number, max);
1135         }
1136         return number;
1137     },
1138
1139     /**
1140      * Formats a number using fixed-point notation
1141      * @param {Number} value The number to format
1142      * @param {Number} precision The number of digits to show after the decimal point
1143      */
1144     toFixed: function(value, precision) {
1145         if (isToFixedBroken) {
1146             precision = precision || 0;
1147             var pow = Math.pow(10, precision);
1148             return (Math.round(value * pow) / pow).toFixed(precision);
1149         }
1150
1151         return value.toFixed(precision);
1152     },
1153
1154     /**
1155      * Validate that a value is numeric and convert it to a number if necessary. Returns the specified default value if
1156      * it is not.
1157
1158 Ext.Number.from('1.23', 1); // returns 1.23
1159 Ext.Number.from('abc', 1); // returns 1
1160
1161      * @param {Mixed} value
1162      * @param {Number} defaultValue The value to return if the original value is non-numeric
1163      * @return {Number} value, if numeric, defaultValue otherwise
1164      */
1165     from: function(value, defaultValue) {
1166         if (isFinite(value)) {
1167             value = parseFloat(value);
1168         }
1169
1170         return !isNaN(value) ? value : defaultValue;
1171     }
1172 };
1173
1174 })();
1175
1176 /**
1177  * This method is deprecated, please use {@link Ext.Number#from Ext.Number.from} instead
1178  *
1179  * @deprecated 4.0.0 Replaced by Ext.Number.from
1180  * @member Ext
1181  * @method num
1182  */
1183 Ext.num = function() {
1184     return Ext.Number.from.apply(this, arguments);
1185 };
1186 /**
1187  * @author Jacky Nguyen <jacky@sencha.com>
1188  * @docauthor Jacky Nguyen <jacky@sencha.com>
1189  * @class Ext.Array
1190  *
1191  * A set of useful static methods to deal with arrays; provide missing methods for older browsers.
1192
1193  * @singleton
1194  * @markdown
1195  */
1196 (function() {
1197
1198     var arrayPrototype = Array.prototype,
1199         slice = arrayPrototype.slice,
1200         supportsForEach = 'forEach' in arrayPrototype,
1201         supportsMap = 'map' in arrayPrototype,
1202         supportsIndexOf = 'indexOf' in arrayPrototype,
1203         supportsEvery = 'every' in arrayPrototype,
1204         supportsSome = 'some' in arrayPrototype,
1205         supportsFilter = 'filter' in arrayPrototype,
1206         supportsSort = function() {
1207             var a = [1,2,3,4,5].sort(function(){ return 0; });
1208             return a[0] === 1 && a[1] === 2 && a[2] === 3 && a[3] === 4 && a[4] === 5;
1209         }(),
1210         supportsSliceOnNodeList = true,
1211         ExtArray;
1212     try {
1213         // IE 6 - 8 will throw an error when using Array.prototype.slice on NodeList
1214         if (typeof document !== 'undefined') {
1215             slice.call(document.getElementsByTagName('body'));
1216         }
1217     } catch (e) {
1218         supportsSliceOnNodeList = false;
1219     }
1220
1221     ExtArray = Ext.Array = {
1222         /*
1223          * Iterates an array or an iterable value and invoke the given callback function for each item.
1224
1225     var countries = ['Vietnam', 'Singapore', 'United States', 'Russia'];
1226
1227     Ext.Array.each(countries, function(name, index, countriesItSelf) {
1228         console.log(name);
1229     });
1230
1231     var sum = function() {
1232         var sum = 0;
1233
1234         Ext.Array.each(arguments, function(value) {
1235             sum += value;
1236         });
1237
1238         return sum;
1239     };
1240
1241     sum(1, 2, 3); // returns 6
1242
1243          * The iteration can be stopped by returning false in the function callback.
1244
1245     Ext.Array.each(countries, function(name, index, countriesItSelf) {
1246         if (name === 'Singapore') {
1247             return false; // break here
1248         }
1249     });
1250
1251          * @param {Array/NodeList/Mixed} iterable The value to be iterated. If this
1252          * argument is not iterable, the callback function is called once.
1253          * @param {Function} fn The callback function. If it returns false, the iteration stops and this method returns
1254          * the current `index`. Arguments passed to this callback function are:
1255
1256 - `item`: {Mixed} The item at the current `index` in the passed `array`
1257 - `index`: {Number} The current `index` within the `array`
1258 - `allItems`: {Array/NodeList/Mixed} The `array` passed as the first argument to `Ext.Array.each`
1259
1260          * @param {Object} scope (Optional) The scope (`this` reference) in which the specified function is executed.
1261          * @param {Boolean} reverse (Optional) Reverse the iteration order (loop from the end to the beginning)
1262          * Defaults false
1263          * @return {Boolean} See description for the `fn` parameter.
1264          * @markdown
1265          */
1266         each: function(array, fn, scope, reverse) {
1267             array = ExtArray.from(array);
1268
1269             var i,
1270                 ln = array.length;
1271
1272             if (reverse !== true) {
1273                 for (i = 0; i < ln; i++) {
1274                     if (fn.call(scope || array[i], array[i], i, array) === false) {
1275                         return i;
1276                     }
1277                 }
1278             }
1279             else {
1280                 for (i = ln - 1; i > -1; i--) {
1281                     if (fn.call(scope || array[i], array[i], i, array) === false) {
1282                         return i;
1283                     }
1284                 }
1285             }
1286
1287             return true;
1288         },
1289
1290         /**
1291          * Iterates an array and invoke the given callback function for each item. Note that this will simply
1292          * delegate to the native Array.prototype.forEach method if supported.
1293          * It doesn't support stopping the iteration by returning false in the callback function like
1294          * {@link Ext.Array#each}. However, performance could be much better in modern browsers comparing with
1295          * {@link Ext.Array#each}
1296          *
1297          * @param {Array} array The array to iterate
1298          * @param {Function} fn The function callback, to be invoked these arguments:
1299          *
1300 - `item`: {Mixed} The item at the current `index` in the passed `array`
1301 - `index`: {Number} The current `index` within the `array`
1302 - `allItems`: {Array} The `array` itself which was passed as the first argument
1303
1304          * @param {Object} scope (Optional) The execution scope (`this`) in which the specified function is executed.
1305          * @markdown
1306          */
1307         forEach: function(array, fn, scope) {
1308             if (supportsForEach) {
1309                 return array.forEach(fn, scope);
1310             }
1311
1312             var i = 0,
1313                 ln = array.length;
1314
1315             for (; i < ln; i++) {
1316                 fn.call(scope, array[i], i, array);
1317             }
1318         },
1319
1320         /**
1321          * Get the index of the provided `item` in the given `array`, a supplement for the
1322          * missing arrayPrototype.indexOf in Internet Explorer.
1323          *
1324          * @param {Array} array The array to check
1325          * @param {Mixed} item The item to look for
1326          * @param {Number} from (Optional) The index at which to begin the search
1327          * @return {Number} The index of item in the array (or -1 if it is not found)
1328          * @markdown
1329          */
1330         indexOf: function(array, item, from) {
1331             if (supportsIndexOf) {
1332                 return array.indexOf(item, from);
1333             }
1334
1335             var i, length = array.length;
1336
1337             for (i = (from < 0) ? Math.max(0, length + from) : from || 0; i < length; i++) {
1338                 if (array[i] === item) {
1339                     return i;
1340                 }
1341             }
1342
1343             return -1;
1344         },
1345
1346         /**
1347          * Checks whether or not the given `array` contains the specified `item`
1348          *
1349          * @param {Array} array The array to check
1350          * @param {Mixed} item The item to look for
1351          * @return {Boolean} True if the array contains the item, false otherwise
1352          * @markdown
1353          */
1354         contains: function(array, item) {
1355             if (supportsIndexOf) {
1356                 return array.indexOf(item) !== -1;
1357             }
1358
1359             var i, ln;
1360
1361             for (i = 0, ln = array.length; i < ln; i++) {
1362                 if (array[i] === item) {
1363                     return true;
1364                 }
1365             }
1366
1367             return false;
1368         },
1369
1370         /**
1371          * Converts any iterable (numeric indices and a length property) into a true array.
1372
1373 function test() {
1374     var args = Ext.Array.toArray(arguments),
1375         fromSecondToLastArgs = Ext.Array.toArray(arguments, 1);
1376
1377     alert(args.join(' '));
1378     alert(fromSecondToLastArgs.join(' '));
1379 }
1380
1381 test('just', 'testing', 'here'); // alerts 'just testing here';
1382                                  // alerts 'testing here';
1383
1384 Ext.Array.toArray(document.getElementsByTagName('div')); // will convert the NodeList into an array
1385 Ext.Array.toArray('splitted'); // returns ['s', 'p', 'l', 'i', 't', 't', 'e', 'd']
1386 Ext.Array.toArray('splitted', 0, 3); // returns ['s', 'p', 'l', 'i']
1387
1388          * @param {Mixed} iterable the iterable object to be turned into a true Array.
1389          * @param {Number} start (Optional) a zero-based index that specifies the start of extraction. Defaults to 0
1390          * @param {Number} end (Optional) a zero-based index that specifies the end of extraction. Defaults to the last
1391          * index of the iterable value
1392          * @return {Array} array
1393          * @markdown
1394          */
1395         toArray: function(iterable, start, end){
1396             if (!iterable || !iterable.length) {
1397                 return [];
1398             }
1399
1400             if (typeof iterable === 'string') {
1401                 iterable = iterable.split('');
1402             }
1403
1404             if (supportsSliceOnNodeList) {
1405                 return slice.call(iterable, start || 0, end || iterable.length);
1406             }
1407
1408             var array = [],
1409                 i;
1410
1411             start = start || 0;
1412             end = end ? ((end < 0) ? iterable.length + end : end) : iterable.length;
1413
1414             for (i = start; i < end; i++) {
1415                 array.push(iterable[i]);
1416             }
1417
1418             return array;
1419         },
1420
1421         /**
1422          * Plucks the value of a property from each item in the Array. Example:
1423          *
1424     Ext.Array.pluck(Ext.query("p"), "className"); // [el1.className, el2.className, ..., elN.className]
1425
1426          * @param {Array|NodeList} array The Array of items to pluck the value from.
1427          * @param {String} propertyName The property name to pluck from each element.
1428          * @return {Array} The value from each item in the Array.
1429          */
1430         pluck: function(array, propertyName) {
1431             var ret = [],
1432                 i, ln, item;
1433
1434             for (i = 0, ln = array.length; i < ln; i++) {
1435                 item = array[i];
1436
1437                 ret.push(item[propertyName]);
1438             }
1439
1440             return ret;
1441         },
1442
1443         /**
1444          * Creates a new array with the results of calling a provided function on every element in this array.
1445          * @param {Array} array
1446          * @param {Function} fn Callback function for each item
1447          * @param {Object} scope Callback function scope
1448          * @return {Array} results
1449          */
1450         map: function(array, fn, scope) {
1451             if (supportsMap) {
1452                 return array.map(fn, scope);
1453             }
1454
1455             var results = [],
1456                 i = 0,
1457                 len = array.length;
1458
1459             for (; i < len; i++) {
1460                 results[i] = fn.call(scope, array[i], i, array);
1461             }
1462
1463             return results;
1464         },
1465
1466         /**
1467          * Executes the specified function for each array element until the function returns a falsy value.
1468          * If such an item is found, the function will return false immediately.
1469          * Otherwise, it will return true.
1470          *
1471          * @param {Array} array
1472          * @param {Function} fn Callback function for each item
1473          * @param {Object} scope Callback function scope
1474          * @return {Boolean} True if no false value is returned by the callback function.
1475          */
1476         every: function(array, fn, scope) {
1477             if (!fn) {
1478                 Ext.Error.raise('Ext.Array.every must have a callback function passed as second argument.');
1479             }
1480             if (supportsEvery) {
1481                 return array.every(fn, scope);
1482             }
1483
1484             var i = 0,
1485                 ln = array.length;
1486
1487             for (; i < ln; ++i) {
1488                 if (!fn.call(scope, array[i], i, array)) {
1489                     return false;
1490                 }
1491             }
1492
1493             return true;
1494         },
1495
1496         /**
1497          * Executes the specified function for each array element until the function returns a truthy value.
1498          * If such an item is found, the function will return true immediately. Otherwise, it will return false.
1499          *
1500          * @param {Array} array
1501          * @param {Function} fn Callback function for each item
1502          * @param {Object} scope Callback function scope
1503          * @return {Boolean} True if the callback function returns a truthy value.
1504          */
1505         some: function(array, fn, scope) {
1506             if (!fn) {
1507                 Ext.Error.raise('Ext.Array.some must have a callback function passed as second argument.');
1508             }
1509             if (supportsSome) {
1510                 return array.some(fn, scope);
1511             }
1512
1513             var i = 0,
1514                 ln = array.length;
1515
1516             for (; i < ln; ++i) {
1517                 if (fn.call(scope, array[i], i, array)) {
1518                     return true;
1519                 }
1520             }
1521
1522             return false;
1523         },
1524
1525         /**
1526          * Filter through an array and remove empty item as defined in {@link Ext#isEmpty Ext.isEmpty}
1527          *
1528          * @see Ext.Array.filter
1529          * @param {Array} array
1530          * @return {Array} results
1531          */
1532         clean: function(array) {
1533             var results = [],
1534                 i = 0,
1535                 ln = array.length,
1536                 item;
1537
1538             for (; i < ln; i++) {
1539                 item = array[i];
1540
1541                 if (!Ext.isEmpty(item)) {
1542                     results.push(item);
1543                 }
1544             }
1545
1546             return results;
1547         },
1548
1549         /**
1550          * Returns a new array with unique items
1551          *
1552          * @param {Array} array
1553          * @return {Array} results
1554          */
1555         unique: function(array) {
1556             var clone = [],
1557                 i = 0,
1558                 ln = array.length,
1559                 item;
1560
1561             for (; i < ln; i++) {
1562                 item = array[i];
1563
1564                 if (ExtArray.indexOf(clone, item) === -1) {
1565                     clone.push(item);
1566                 }
1567             }
1568
1569             return clone;
1570         },
1571
1572         /**
1573          * Creates a new array with all of the elements of this array for which
1574          * the provided filtering function returns true.
1575          * @param {Array} array
1576          * @param {Function} fn Callback function for each item
1577          * @param {Object} scope Callback function scope
1578          * @return {Array} results
1579          */
1580         filter: function(array, fn, scope) {
1581             if (supportsFilter) {
1582                 return array.filter(fn, scope);
1583             }
1584
1585             var results = [],
1586                 i = 0,
1587                 ln = array.length;
1588
1589             for (; i < ln; i++) {
1590                 if (fn.call(scope, array[i], i, array)) {
1591                     results.push(array[i]);
1592                 }
1593             }
1594
1595             return results;
1596         },
1597
1598         /**
1599          * Converts a value to an array if it's not already an array; returns:
1600          *
1601          * - An empty array if given value is `undefined` or `null`
1602          * - Itself if given value is already an array
1603          * - An array copy if given value is {@link Ext#isIterable iterable} (arguments, NodeList and alike)
1604          * - An array with one item which is the given value, otherwise
1605          *
1606          * @param {Array/Mixed} value The value to convert to an array if it's not already is an array
1607          * @param {Boolean} (Optional) newReference True to clone the given array and return a new reference if necessary,
1608          * defaults to false
1609          * @return {Array} array
1610          * @markdown
1611          */
1612         from: function(value, newReference) {
1613             if (value === undefined || value === null) {
1614                 return [];
1615             }
1616
1617             if (Ext.isArray(value)) {
1618                 return (newReference) ? slice.call(value) : value;
1619             }
1620
1621             if (value && value.length !== undefined && typeof value !== 'string') {
1622                 return Ext.toArray(value);
1623             }
1624
1625             return [value];
1626         },
1627
1628         /**
1629          * Removes the specified item from the array if it exists
1630          *
1631          * @param {Array} array The array
1632          * @param {Mixed} item The item to remove
1633          * @return {Array} The passed array itself
1634          */
1635         remove: function(array, item) {
1636             var index = ExtArray.indexOf(array, item);
1637
1638             if (index !== -1) {
1639                 array.splice(index, 1);
1640             }
1641
1642             return array;
1643         },
1644
1645         /**
1646          * Push an item into the array only if the array doesn't contain it yet
1647          *
1648          * @param {Array} array The array
1649          * @param {Mixed} item The item to include
1650          * @return {Array} The passed array itself
1651          */
1652         include: function(array, item) {
1653             if (!ExtArray.contains(array, item)) {
1654                 array.push(item);
1655             }
1656         },
1657
1658         /**
1659          * Clone a flat array without referencing the previous one. Note that this is different
1660          * from Ext.clone since it doesn't handle recursive cloning. It's simply a convenient, easy-to-remember method
1661          * for Array.prototype.slice.call(array)
1662          *
1663          * @param {Array} array The array
1664          * @return {Array} The clone array
1665          */
1666         clone: function(array) {
1667             return slice.call(array);
1668         },
1669
1670         /**
1671          * Merge multiple arrays into one with unique items. Alias to {@link Ext.Array#union}.
1672          *
1673          * @param {Array} array,...
1674          * @return {Array} merged
1675          */
1676         merge: function() {
1677             var args = slice.call(arguments),
1678                 array = [],
1679                 i, ln;
1680
1681             for (i = 0, ln = args.length; i < ln; i++) {
1682                 array = array.concat(args[i]);
1683             }
1684
1685             return ExtArray.unique(array);
1686         },
1687
1688         /**
1689          * Merge multiple arrays into one with unique items that exist in all of the arrays.
1690          *
1691          * @param {Array} array,...
1692          * @return {Array} intersect
1693          */
1694         intersect: function() {
1695             var intersect = [],
1696                 arrays = slice.call(arguments),
1697                 i, j, k, minArray, array, x, y, ln, arraysLn, arrayLn;
1698
1699             if (!arrays.length) {
1700                 return intersect;
1701             }
1702
1703             // Find the smallest array
1704             for (i = x = 0,ln = arrays.length; i < ln,array = arrays[i]; i++) {
1705                 if (!minArray || array.length < minArray.length) {
1706                     minArray = array;
1707                     x = i;
1708                 }
1709             }
1710
1711             minArray = Ext.Array.unique(minArray);
1712             arrays.splice(x, 1);
1713
1714             // Use the smallest unique'd array as the anchor loop. If the other array(s) do contain
1715             // an item in the small array, we're likely to find it before reaching the end
1716             // of the inner loop and can terminate the search early.
1717             for (i = 0,ln = minArray.length; i < ln,x = minArray[i]; i++) {
1718                 var count = 0;
1719
1720                 for (j = 0,arraysLn = arrays.length; j < arraysLn,array = arrays[j]; j++) {
1721                     for (k = 0,arrayLn = array.length; k < arrayLn,y = array[k]; k++) {
1722                         if (x === y) {
1723                             count++;
1724                             break;
1725                         }
1726                     }
1727                 }
1728
1729                 if (count === arraysLn) {
1730                     intersect.push(x);
1731                 }
1732             }
1733
1734             return intersect;
1735         },
1736
1737         /**
1738          * Perform a set difference A-B by subtracting all items in array B from array A.
1739          *
1740          * @param {Array} array A
1741          * @param {Array} array B
1742          * @return {Array} difference
1743          */
1744         difference: function(arrayA, arrayB) {
1745             var clone = slice.call(arrayA),
1746                 ln = clone.length,
1747                 i, j, lnB;
1748
1749             for (i = 0,lnB = arrayB.length; i < lnB; i++) {
1750                 for (j = 0; j < ln; j++) {
1751                     if (clone[j] === arrayB[i]) {
1752                         clone.splice(j, 1);
1753                         j--;
1754                         ln--;
1755                     }
1756                 }
1757             }
1758
1759             return clone;
1760         },
1761
1762         /**
1763          * Sorts the elements of an Array.
1764          * By default, this method sorts the elements alphabetically and ascending.
1765          *
1766          * @param {Array} array The array to sort.
1767          * @param {Function} sortFn (optional) The comparison function.
1768          * @return {Array} The sorted array.
1769          */
1770         sort: function(array, sortFn) {
1771             if (supportsSort) {
1772                 if (sortFn) {
1773                     return array.sort(sortFn);
1774                 } else {
1775                     return array.sort();
1776                 }
1777             }
1778
1779             var length = array.length,
1780                 i = 0,
1781                 comparison,
1782                 j, min, tmp;
1783
1784             for (; i < length; i++) {
1785                 min = i;
1786                 for (j = i + 1; j < length; j++) {
1787                     if (sortFn) {
1788                         comparison = sortFn(array[j], array[min]);
1789                         if (comparison < 0) {
1790                             min = j;
1791                         }
1792                     } else if (array[j] < array[min]) {
1793                         min = j;
1794                     }
1795                 }
1796                 if (min !== i) {
1797                     tmp = array[i];
1798                     array[i] = array[min];
1799                     array[min] = tmp;
1800                 }
1801             }
1802
1803             return array;
1804         },
1805
1806         /**
1807          * Recursively flattens into 1-d Array. Injects Arrays inline.
1808          * @param {Array} array The array to flatten
1809          * @return {Array} The new, flattened array.
1810          */
1811         flatten: function(array) {
1812             var worker = [];
1813
1814             function rFlatten(a) {
1815                 var i, ln, v;
1816
1817                 for (i = 0, ln = a.length; i < ln; i++) {
1818                     v = a[i];
1819
1820                     if (Ext.isArray(v)) {
1821                         rFlatten(v);
1822                     } else {
1823                         worker.push(v);
1824                     }
1825                 }
1826
1827                 return worker;
1828             }
1829
1830             return rFlatten(array);
1831         },
1832
1833         /**
1834          * Returns the minimum value in the Array.
1835          * @param {Array|NodeList} array The Array from which to select the minimum value.
1836          * @param {Function} comparisonFn (optional) a function to perform the comparision which determines minimization.
1837          *                   If omitted the "<" operator will be used. Note: gt = 1; eq = 0; lt = -1
1838          * @return {Mixed} minValue The minimum value
1839          */
1840         min: function(array, comparisonFn) {
1841             var min = array[0],
1842                 i, ln, item;
1843
1844             for (i = 0, ln = array.length; i < ln; i++) {
1845                 item = array[i];
1846
1847                 if (comparisonFn) {
1848                     if (comparisonFn(min, item) === 1) {
1849                         min = item;
1850                     }
1851                 }
1852                 else {
1853                     if (item < min) {
1854                         min = item;
1855                     }
1856                 }
1857             }
1858
1859             return min;
1860         },
1861
1862         /**
1863          * Returns the maximum value in the Array
1864          * @param {Array|NodeList} array The Array from which to select the maximum value.
1865          * @param {Function} comparisonFn (optional) a function to perform the comparision which determines maximization.
1866          *                   If omitted the ">" operator will be used. Note: gt = 1; eq = 0; lt = -1
1867          * @return {Mixed} maxValue The maximum value
1868          */
1869         max: function(array, comparisonFn) {
1870             var max = array[0],
1871                 i, ln, item;
1872
1873             for (i = 0, ln = array.length; i < ln; i++) {
1874                 item = array[i];
1875
1876                 if (comparisonFn) {
1877                     if (comparisonFn(max, item) === -1) {
1878                         max = item;
1879                     }
1880                 }
1881                 else {
1882                     if (item > max) {
1883                         max = item;
1884                     }
1885                 }
1886             }
1887
1888             return max;
1889         },
1890
1891         /**
1892          * Calculates the mean of all items in the array
1893          * @param {Array} array The Array to calculate the mean value of.
1894          * @return {Number} The mean.
1895          */
1896         mean: function(array) {
1897             return array.length > 0 ? ExtArray.sum(array) / array.length : undefined;
1898         },
1899
1900         /**
1901          * Calculates the sum of all items in the given array
1902          * @param {Array} array The Array to calculate the sum value of.
1903          * @return {Number} The sum.
1904          */
1905         sum: function(array) {
1906             var sum = 0,
1907                 i, ln, item;
1908
1909             for (i = 0,ln = array.length; i < ln; i++) {
1910                 item = array[i];
1911
1912                 sum += item;
1913             }
1914
1915             return sum;
1916         }
1917
1918     };
1919
1920     /**
1921      * Convenient alias to {@link Ext.Array#each}
1922      * @member Ext
1923      * @method each
1924      */
1925     Ext.each = Ext.Array.each;
1926
1927     /**
1928      * Alias to {@link Ext.Array#merge}.
1929      * @member Ext.Array
1930      * @method union
1931      */
1932     Ext.Array.union = Ext.Array.merge;
1933
1934     /**
1935      * Old alias to {@link Ext.Array#min}
1936      * @deprecated 4.0.0 Use {@link Ext.Array#min} instead
1937      * @member Ext
1938      * @method min
1939      */
1940     Ext.min = Ext.Array.min;
1941
1942     /**
1943      * Old alias to {@link Ext.Array#max}
1944      * @deprecated 4.0.0 Use {@link Ext.Array#max} instead
1945      * @member Ext
1946      * @method max
1947      */
1948     Ext.max = Ext.Array.max;
1949
1950     /**
1951      * Old alias to {@link Ext.Array#sum}
1952      * @deprecated 4.0.0 Use {@link Ext.Array#sum} instead
1953      * @member Ext
1954      * @method sum
1955      */
1956     Ext.sum = Ext.Array.sum;
1957
1958     /**
1959      * Old alias to {@link Ext.Array#mean}
1960      * @deprecated 4.0.0 Use {@link Ext.Array#mean} instead
1961      * @member Ext
1962      * @method mean
1963      */
1964     Ext.mean = Ext.Array.mean;
1965
1966     /**
1967      * Old alias to {@link Ext.Array#flatten}
1968      * @deprecated 4.0.0 Use {@link Ext.Array#flatten} instead
1969      * @member Ext
1970      * @method flatten
1971      */
1972     Ext.flatten = Ext.Array.flatten;
1973
1974     /**
1975      * Old alias to {@link Ext.Array#clean Ext.Array.clean}
1976      * @deprecated 4.0.0 Use {@link Ext.Array.clean} instead
1977      * @member Ext
1978      * @method clean
1979      */
1980     Ext.clean = Ext.Array.clean;
1981
1982     /**
1983      * Old alias to {@link Ext.Array#unique Ext.Array.unique}
1984      * @deprecated 4.0.0 Use {@link Ext.Array.unique} instead
1985      * @member Ext
1986      * @method unique
1987      */
1988     Ext.unique = Ext.Array.unique;
1989
1990     /**
1991      * Old alias to {@link Ext.Array#pluck Ext.Array.pluck}
1992      * @deprecated 4.0.0 Use {@link Ext.Array#pluck Ext.Array.pluck} instead
1993      * @member Ext
1994      * @method pluck
1995      */
1996     Ext.pluck = Ext.Array.pluck;
1997
1998     /**
1999      * Convenient alias to {@link Ext.Array#toArray Ext.Array.toArray}
2000      * @param {Iterable} the iterable object to be turned into a true Array.
2001      * @member Ext
2002      * @method toArray
2003      * @return {Array} array
2004      */
2005     Ext.toArray = function() {
2006         return ExtArray.toArray.apply(ExtArray, arguments);
2007     }
2008 })();
2009
2010 /**
2011  * @class Ext.Function
2012  *
2013  * A collection of useful static methods to deal with function callbacks
2014  * @singleton
2015  */
2016
2017 Ext.Function = {
2018
2019     /**
2020      * A very commonly used method throughout the framework. It acts as a wrapper around another method
2021      * which originally accepts 2 arguments for <code>name</code> and <code>value</code>.
2022      * The wrapped function then allows "flexible" value setting of either:
2023      *
2024      * <ul>
2025      *      <li><code>name</code> and <code>value</code> as 2 arguments</li>
2026      *      <li>one single object argument with multiple key - value pairs</li>
2027      * </ul>
2028      *
2029      * For example:
2030      * <pre><code>
2031 var setValue = Ext.Function.flexSetter(function(name, value) {
2032     this[name] = value;
2033 });
2034
2035 // Afterwards
2036 // Setting a single name - value
2037 setValue('name1', 'value1');
2038
2039 // Settings multiple name - value pairs
2040 setValue({
2041     name1: 'value1',
2042     name2: 'value2',
2043     name3: 'value3'
2044 });
2045      * </code></pre>
2046      * @param {Function} setter
2047      * @returns {Function} flexSetter
2048      */
2049     flexSetter: function(fn) {
2050         return function(a, b) {
2051             var k, i;
2052
2053             if (a === null) {
2054                 return this;
2055             }
2056
2057             if (typeof a !== 'string') {
2058                 for (k in a) {
2059                     if (a.hasOwnProperty(k)) {
2060                         fn.call(this, k, a[k]);
2061                     }
2062                 }
2063
2064                 if (Ext.enumerables) {
2065                     for (i = Ext.enumerables.length; i--;) {
2066                         k = Ext.enumerables[i];
2067                         if (a.hasOwnProperty(k)) {
2068                             fn.call(this, k, a[k]);
2069                         }
2070                     }
2071                 }
2072             } else {
2073                 fn.call(this, a, b);
2074             }
2075
2076             return this;
2077         };
2078     },
2079
2080    /**
2081      * Create a new function from the provided <code>fn</code>, change <code>this</code> to the provided scope, optionally
2082      * overrides arguments for the call. (Defaults to the arguments passed by the caller)
2083      *
2084      * @param {Function} fn The function to delegate.
2085      * @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which the function is executed.
2086      * <b>If omitted, defaults to the browser window.</b>
2087      * @param {Array} args (optional) Overrides arguments for the call. (Defaults to the arguments passed by the caller)
2088      * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
2089      * if a number the args are inserted at the specified position
2090      * @return {Function} The new function
2091      */
2092     bind: function(fn, scope, args, appendArgs) {
2093         var method = fn,
2094             applyArgs;
2095
2096         return function() {
2097             var callArgs = args || arguments;
2098
2099             if (appendArgs === true) {
2100                 callArgs = Array.prototype.slice.call(arguments, 0);
2101                 callArgs = callArgs.concat(args);
2102             }
2103             else if (Ext.isNumber(appendArgs)) {
2104                 callArgs = Array.prototype.slice.call(arguments, 0); // copy arguments first
2105                 applyArgs = [appendArgs, 0].concat(args); // create method call params
2106                 Array.prototype.splice.apply(callArgs, applyArgs); // splice them in
2107             }
2108
2109             return method.apply(scope || window, callArgs);
2110         };
2111     },
2112
2113     /**
2114      * Create a new function from the provided <code>fn</code>, the arguments of which are pre-set to `args`.
2115      * New arguments passed to the newly created callback when it's invoked are appended after the pre-set ones.
2116      * This is especially useful when creating callbacks.
2117      * For example:
2118      *
2119     var originalFunction = function(){
2120         alert(Ext.Array.from(arguments).join(' '));
2121     };
2122
2123     var callback = Ext.Function.pass(originalFunction, ['Hello', 'World']);
2124
2125     callback(); // alerts 'Hello World'
2126     callback('by Me'); // alerts 'Hello World by Me'
2127
2128      * @param {Function} fn The original function
2129      * @param {Array} args The arguments to pass to new callback
2130      * @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which the function is executed.
2131      * @return {Function} The new callback function
2132      */
2133     pass: function(fn, args, scope) {
2134         if (args) {
2135             args = Ext.Array.from(args);
2136         }
2137
2138         return function() {
2139             return fn.apply(scope, args.concat(Ext.Array.toArray(arguments)));
2140         };
2141     },
2142
2143     /**
2144      * Create an alias to the provided method property with name <code>methodName</code> of <code>object</code>.
2145      * Note that the execution scope will still be bound to the provided <code>object</code> itself.
2146      *
2147      * @param {Object/Function} object
2148      * @param {String} methodName
2149      * @return {Function} aliasFn
2150      */
2151     alias: function(object, methodName) {
2152         return function() {
2153             return object[methodName].apply(object, arguments);
2154         };
2155     },
2156
2157     /**
2158      * Creates an interceptor function. The passed function is called before the original one. If it returns false,
2159      * the original one is not called. The resulting function returns the results of the original function.
2160      * The passed function is called with the parameters of the original function. Example usage:
2161      * <pre><code>
2162 var sayHi = function(name){
2163     alert('Hi, ' + name);
2164 }
2165
2166 sayHi('Fred'); // alerts "Hi, Fred"
2167
2168 // create a new function that validates input without
2169 // directly modifying the original function:
2170 var sayHiToFriend = Ext.Function.createInterceptor(sayHi, function(name){
2171     return name == 'Brian';
2172 });
2173
2174 sayHiToFriend('Fred');  // no alert
2175 sayHiToFriend('Brian'); // alerts "Hi, Brian"
2176      </code></pre>
2177      * @param {Function} origFn The original function.
2178      * @param {Function} newFn The function to call before the original
2179      * @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which the passed function is executed.
2180      * <b>If omitted, defaults to the scope in which the original function is called or the browser window.</b>
2181      * @param {Mixed} returnValue (optional) The value to return if the passed function return false (defaults to null).
2182      * @return {Function} The new function
2183      */
2184     createInterceptor: function(origFn, newFn, scope, returnValue) {
2185         var method = origFn;
2186         if (!Ext.isFunction(newFn)) {
2187             return origFn;
2188         }
2189         else {
2190             return function() {
2191                 var me = this,
2192                     args = arguments;
2193                 newFn.target = me;
2194                 newFn.method = origFn;
2195                 return (newFn.apply(scope || me || window, args) !== false) ? origFn.apply(me || window, args) : returnValue || null;
2196             };
2197         }
2198     },
2199
2200     /**
2201     * Creates a delegate (callback) which, when called, executes after a specific delay.
2202     * @param {Function} fn The function which will be called on a delay when the returned function is called.
2203     * Optionally, a replacement (or additional) argument list may be specified.
2204     * @param {Number} delay The number of milliseconds to defer execution by whenever called.
2205     * @param {Object} scope (optional) The scope (<code>this</code> reference) used by the function at execution time.
2206     * @param {Array} args (optional) Override arguments for the call. (Defaults to the arguments passed by the caller)
2207     * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
2208     * if a number the args are inserted at the specified position.
2209     * @return {Function} A function which, when called, executes the original function after the specified delay.
2210     */
2211     createDelayed: function(fn, delay, scope, args, appendArgs) {
2212         if (scope || args) {
2213             fn = Ext.Function.bind(fn, scope, args, appendArgs);
2214         }
2215         return function() {
2216             var me = this;
2217             setTimeout(function() {
2218                 fn.apply(me, arguments);
2219             }, delay);
2220         };
2221     },
2222
2223     /**
2224      * Calls this function after the number of millseconds specified, optionally in a specific scope. Example usage:
2225      * <pre><code>
2226 var sayHi = function(name){
2227     alert('Hi, ' + name);
2228 }
2229
2230 // executes immediately:
2231 sayHi('Fred');
2232
2233 // executes after 2 seconds:
2234 Ext.Function.defer(sayHi, 2000, this, ['Fred']);
2235
2236 // this syntax is sometimes useful for deferring
2237 // execution of an anonymous function:
2238 Ext.Function.defer(function(){
2239     alert('Anonymous');
2240 }, 100);
2241      </code></pre>
2242      * @param {Function} fn The function to defer.
2243      * @param {Number} millis The number of milliseconds for the setTimeout call (if less than or equal to 0 the function is executed immediately)
2244      * @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which the function is executed.
2245      * <b>If omitted, defaults to the browser window.</b>
2246      * @param {Array} args (optional) Overrides arguments for the call. (Defaults to the arguments passed by the caller)
2247      * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
2248      * if a number the args are inserted at the specified position
2249      * @return {Number} The timeout id that can be used with clearTimeout
2250      */
2251     defer: function(fn, millis, obj, args, appendArgs) {
2252         fn = Ext.Function.bind(fn, obj, args, appendArgs);
2253         if (millis > 0) {
2254             return setTimeout(fn, millis);
2255         }
2256         fn();
2257         return 0;
2258     },
2259
2260     /**
2261      * Create a combined function call sequence of the original function + the passed function.
2262      * The resulting function returns the results of the original function.
2263      * The passed function is called with the parameters of the original function. Example usage:
2264      *
2265      * <pre><code>
2266 var sayHi = function(name){
2267     alert('Hi, ' + name);
2268 }
2269
2270 sayHi('Fred'); // alerts "Hi, Fred"
2271
2272 var sayGoodbye = Ext.Function.createSequence(sayHi, function(name){
2273     alert('Bye, ' + name);
2274 });
2275
2276 sayGoodbye('Fred'); // both alerts show
2277      * </code></pre>
2278      *
2279      * @param {Function} origFn The original function.
2280      * @param {Function} newFn The function to sequence
2281      * @param {Object} scope (optional) The scope (this reference) in which the passed function is executed.
2282      * If omitted, defaults to the scope in which the original function is called or the browser window.
2283      * @return {Function} The new function
2284      */
2285     createSequence: function(origFn, newFn, scope) {
2286         if (!Ext.isFunction(newFn)) {
2287             return origFn;
2288         }
2289         else {
2290             return function() {
2291                 var retval = origFn.apply(this || window, arguments);
2292                 newFn.apply(scope || this || window, arguments);
2293                 return retval;
2294             };
2295         }
2296     },
2297
2298     /**
2299      * <p>Creates a delegate function, optionally with a bound scope which, when called, buffers
2300      * the execution of the passed function for the configured number of milliseconds.
2301      * If called again within that period, the impending invocation will be canceled, and the
2302      * timeout period will begin again.</p>
2303      *
2304      * @param {Function} fn The function to invoke on a buffered timer.
2305      * @param {Number} buffer The number of milliseconds by which to buffer the invocation of the
2306      * function.
2307      * @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which
2308      * the passed function is executed. If omitted, defaults to the scope specified by the caller.
2309      * @param {Array} args (optional) Override arguments for the call. Defaults to the arguments
2310      * passed by the caller.
2311      * @return {Function} A function which invokes the passed function after buffering for the specified time.
2312      */
2313     createBuffered: function(fn, buffer, scope, args) {
2314         return function(){
2315             var timerId;
2316             return function() {
2317                 var me = this;
2318                 if (timerId) {
2319                     clearInterval(timerId);
2320                     timerId = null;
2321                 }
2322                 timerId = setTimeout(function(){
2323                     fn.apply(scope || me, args || arguments);
2324                 }, buffer);
2325             };
2326         }();
2327     },
2328
2329     /**
2330      * <p>Creates a throttled version of the passed function which, when called repeatedly and
2331      * rapidly, invokes the passed function only after a certain interval has elapsed since the
2332      * previous invocation.</p>
2333      *
2334      * <p>This is useful for wrapping functions which may be called repeatedly, such as
2335      * a handler of a mouse move event when the processing is expensive.</p>
2336      *
2337      * @param fn {Function} The function to execute at a regular time interval.
2338      * @param interval {Number} The interval <b>in milliseconds</b> on which the passed function is executed.
2339      * @param scope (optional) The scope (<code><b>this</b></code> reference) in which
2340      * the passed function is executed. If omitted, defaults to the scope specified by the caller.
2341      * @returns {Function} A function which invokes the passed function at the specified interval.
2342      */
2343     createThrottled: function(fn, interval, scope) {
2344         var lastCallTime, elapsed, lastArgs, timer, execute = function() {
2345             fn.apply(scope || this, lastArgs);
2346             lastCallTime = new Date().getTime();
2347         };
2348
2349         return function() {
2350             elapsed = new Date().getTime() - lastCallTime;
2351             lastArgs = arguments;
2352
2353             clearTimeout(timer);
2354             if (!lastCallTime || (elapsed >= interval)) {
2355                 execute();
2356             } else {
2357                 timer = setTimeout(execute, interval - elapsed);
2358             }
2359         };
2360     }
2361 };
2362
2363 /**
2364  * Shorthand for {@link Ext.Function#defer}
2365  * @member Ext
2366  * @method defer
2367  */
2368 Ext.defer = Ext.Function.alias(Ext.Function, 'defer');
2369
2370 /**
2371  * Shorthand for {@link Ext.Function#pass}
2372  * @member Ext
2373  * @method pass
2374  */
2375 Ext.pass = Ext.Function.alias(Ext.Function, 'pass');
2376
2377 /**
2378  * Shorthand for {@link Ext.Function#bind}
2379  * @member Ext
2380  * @method bind
2381  */
2382 Ext.bind = Ext.Function.alias(Ext.Function, 'bind');
2383
2384 /**
2385  * @author Jacky Nguyen <jacky@sencha.com>
2386  * @docauthor Jacky Nguyen <jacky@sencha.com>
2387  * @class Ext.Object
2388  *
2389  * A collection of useful static methods to deal with objects
2390  *
2391  * @singleton
2392  */
2393
2394 (function() {
2395
2396 var ExtObject = Ext.Object = {
2397
2398     /**
2399      * Convert a `name` - `value` pair to an array of objects with support for nested structures; useful to construct
2400      * query strings. For example:
2401
2402     var objects = Ext.Object.toQueryObjects('hobbies', ['reading', 'cooking', 'swimming']);
2403
2404     // objects then equals:
2405     [
2406         { name: 'hobbies', value: 'reading' },
2407         { name: 'hobbies', value: 'cooking' },
2408         { name: 'hobbies', value: 'swimming' },
2409     ];
2410
2411     var objects = Ext.Object.toQueryObjects('dateOfBirth', {
2412         day: 3,
2413         month: 8,
2414         year: 1987,
2415         extra: {
2416             hour: 4
2417             minute: 30
2418         }
2419     }, true); // Recursive
2420
2421     // objects then equals:
2422     [
2423         { name: 'dateOfBirth[day]', value: 3 },
2424         { name: 'dateOfBirth[month]', value: 8 },
2425         { name: 'dateOfBirth[year]', value: 1987 },
2426         { name: 'dateOfBirth[extra][hour]', value: 4 },
2427         { name: 'dateOfBirth[extra][minute]', value: 30 },
2428     ];
2429
2430      * @param {String} name
2431      * @param {Mixed} value
2432      * @param {Boolean} recursive
2433      * @markdown
2434      */
2435     toQueryObjects: function(name, value, recursive) {
2436         var self = ExtObject.toQueryObjects,
2437             objects = [],
2438             i, ln;
2439
2440         if (Ext.isArray(value)) {
2441             for (i = 0, ln = value.length; i < ln; i++) {
2442                 if (recursive) {
2443                     objects = objects.concat(self(name + '[' + i + ']', value[i], true));
2444                 }
2445                 else {
2446                     objects.push({
2447                         name: name,
2448                         value: value[i]
2449                     });
2450                 }
2451             }
2452         }
2453         else if (Ext.isObject(value)) {
2454             for (i in value) {
2455                 if (value.hasOwnProperty(i)) {
2456                     if (recursive) {
2457                         objects = objects.concat(self(name + '[' + i + ']', value[i], true));
2458                     }
2459                     else {
2460                         objects.push({
2461                             name: name,
2462                             value: value[i]
2463                         });
2464                     }
2465                 }
2466             }
2467         }
2468         else {
2469             objects.push({
2470                 name: name,
2471                 value: value
2472             });
2473         }
2474
2475         return objects;
2476     },
2477
2478     /**
2479      * Takes an object and converts it to an encoded query string
2480
2481 - Non-recursive:
2482
2483     Ext.Object.toQueryString({foo: 1, bar: 2}); // returns "foo=1&bar=2"
2484     Ext.Object.toQueryString({foo: null, bar: 2}); // returns "foo=&bar=2"
2485     Ext.Object.toQueryString({'some price': '$300'}); // returns "some%20price=%24300"
2486     Ext.Object.toQueryString({date: new Date(2011, 0, 1)}); // returns "date=%222011-01-01T00%3A00%3A00%22"
2487     Ext.Object.toQueryString({colors: ['red', 'green', 'blue']}); // returns "colors=red&colors=green&colors=blue"
2488
2489 - Recursive:
2490
2491     Ext.Object.toQueryString({
2492         username: 'Jacky',
2493         dateOfBirth: {
2494             day: 1,
2495             month: 2,
2496             year: 1911
2497         },
2498         hobbies: ['coding', 'eating', 'sleeping', ['nested', 'stuff']]
2499     }, true); // returns the following string (broken down and url-decoded for ease of reading purpose):
2500               // username=Jacky
2501               //    &dateOfBirth[day]=1&dateOfBirth[month]=2&dateOfBirth[year]=1911
2502               //    &hobbies[0]=coding&hobbies[1]=eating&hobbies[2]=sleeping&hobbies[3][0]=nested&hobbies[3][1]=stuff
2503
2504      *
2505      * @param {Object} object The object to encode
2506      * @param {Boolean} recursive (optional) Whether or not to interpret the object in recursive format.
2507      * (PHP / Ruby on Rails servers and similar). Defaults to false
2508      * @return {String} queryString
2509      * @markdown
2510      */
2511     toQueryString: function(object, recursive) {
2512         var paramObjects = [],
2513             params = [],
2514             i, j, ln, paramObject, value;
2515
2516         for (i in object) {
2517             if (object.hasOwnProperty(i)) {
2518                 paramObjects = paramObjects.concat(ExtObject.toQueryObjects(i, object[i], recursive));
2519             }
2520         }
2521
2522         for (j = 0, ln = paramObjects.length; j < ln; j++) {
2523             paramObject = paramObjects[j];
2524             value = paramObject.value;
2525
2526             if (Ext.isEmpty(value)) {
2527                 value = '';
2528             }
2529             else if (Ext.isDate(value)) {
2530                 value = Ext.Date.toString(value);
2531             }
2532
2533             params.push(encodeURIComponent(paramObject.name) + '=' + encodeURIComponent(String(value)));
2534         }
2535
2536         return params.join('&');
2537     },
2538
2539     /**
2540      * Converts a query string back into an object.
2541      *
2542 - Non-recursive:
2543
2544     Ext.Object.fromQueryString(foo=1&bar=2); // returns {foo: 1, bar: 2}
2545     Ext.Object.fromQueryString(foo=&bar=2); // returns {foo: null, bar: 2}
2546     Ext.Object.fromQueryString(some%20price=%24300); // returns {'some price': '$300'}
2547     Ext.Object.fromQueryString(colors=red&colors=green&colors=blue); // returns {colors: ['red', 'green', 'blue']}
2548
2549 - Recursive:
2550
2551     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);
2552
2553     // returns
2554     {
2555         username: 'Jacky',
2556         dateOfBirth: {
2557             day: '1',
2558             month: '2',
2559             year: '1911'
2560         },
2561         hobbies: ['coding', 'eating', 'sleeping', ['nested', 'stuff']]
2562     }
2563
2564      * @param {String} queryString The query string to decode
2565      * @param {Boolean} recursive (Optional) Whether or not to recursively decode the string. This format is supported by
2566      * PHP / Ruby on Rails servers and similar. Defaults to false
2567      * @return {Object}
2568      */
2569     fromQueryString: function(queryString, recursive) {
2570         var parts = queryString.replace(/^\?/, '').split('&'),
2571             object = {},
2572             temp, components, name, value, i, ln,
2573             part, j, subLn, matchedKeys, matchedName,
2574             keys, key, nextKey;
2575
2576         for (i = 0, ln = parts.length; i < ln; i++) {
2577             part = parts[i];
2578
2579             if (part.length > 0) {
2580                 components = part.split('=');
2581                 name = decodeURIComponent(components[0]);
2582                 value = (components[1] !== undefined) ? decodeURIComponent(components[1]) : '';
2583
2584                 if (!recursive) {
2585                     if (object.hasOwnProperty(name)) {
2586                         if (!Ext.isArray(object[name])) {
2587                             object[name] = [object[name]];
2588                         }
2589
2590                         object[name].push(value);
2591                     }
2592                     else {
2593                         object[name] = value;
2594                     }
2595                 }
2596                 else {
2597                     matchedKeys = name.match(/(\[):?([^\]]*)\]/g);
2598                     matchedName = name.match(/^([^\[]+)/);
2599
2600                     if (!matchedName) {
2601                         Ext.Error.raise({
2602                             sourceClass: "Ext.Object",
2603                             sourceMethod: "fromQueryString",
2604                             queryString: queryString,
2605                             recursive: recursive,
2606                             msg: 'Malformed query string given, failed parsing name from "' + part + '"'
2607                         });
2608                     }
2609
2610                     name = matchedName[0];
2611                     keys = [];
2612
2613                     if (matchedKeys === null) {
2614                         object[name] = value;
2615                         continue;
2616                     }
2617
2618                     for (j = 0, subLn = matchedKeys.length; j < subLn; j++) {
2619                         key = matchedKeys[j];
2620                         key = (key.length === 2) ? '' : key.substring(1, key.length - 1);
2621                         keys.push(key);
2622                     }
2623
2624                     keys.unshift(name);
2625
2626                     temp = object;
2627
2628                     for (j = 0, subLn = keys.length; j < subLn; j++) {
2629                         key = keys[j];
2630
2631                         if (j === subLn - 1) {
2632                             if (Ext.isArray(temp) && key === '') {
2633                                 temp.push(value);
2634                             }
2635                             else {
2636                                 temp[key] = value;
2637                             }
2638                         }
2639                         else {
2640                             if (temp[key] === undefined || typeof temp[key] === 'string') {
2641                                 nextKey = keys[j+1];
2642
2643                                 temp[key] = (Ext.isNumeric(nextKey) || nextKey === '') ? [] : {};
2644                             }
2645
2646                             temp = temp[key];
2647                         }
2648                     }
2649                 }
2650             }
2651         }
2652
2653         return object;
2654     },
2655
2656     /**
2657      * Iterate through an object and invoke the given callback function for each iteration. The iteration can be stop
2658      * by returning `false` in the callback function. For example:
2659
2660     var person = {
2661         name: 'Jacky'
2662         hairColor: 'black'
2663         loves: ['food', 'sleeping', 'wife']
2664     };
2665
2666     Ext.Object.each(person, function(key, value, myself) {
2667         console.log(key + ":" + value);
2668
2669         if (key === 'hairColor') {
2670             return false; // stop the iteration
2671         }
2672     });
2673
2674      * @param {Object} object The object to iterate
2675      * @param {Function} fn The callback function. Passed arguments for each iteration are:
2676
2677 - {String} `key`
2678 - {Mixed} `value`
2679 - {Object} `object` The object itself
2680
2681      * @param {Object} scope (Optional) The execution scope (`this`) of the callback function
2682      * @markdown
2683      */
2684     each: function(object, fn, scope) {
2685         for (var property in object) {
2686             if (object.hasOwnProperty(property)) {
2687                 if (fn.call(scope || object, property, object[property], object) === false) {
2688                     return;
2689                 }
2690             }
2691         }
2692     },
2693
2694     /**
2695      * Merges any number of objects recursively without referencing them or their children.
2696
2697     var extjs = {
2698         companyName: 'Ext JS',
2699         products: ['Ext JS', 'Ext GWT', 'Ext Designer'],
2700         isSuperCool: true
2701         office: {
2702             size: 2000,
2703             location: 'Palo Alto',
2704             isFun: true
2705         }
2706     };
2707
2708     var newStuff = {
2709         companyName: 'Sencha Inc.',
2710         products: ['Ext JS', 'Ext GWT', 'Ext Designer', 'Sencha Touch', 'Sencha Animator'],
2711         office: {
2712             size: 40000,
2713             location: 'Redwood City'
2714         }
2715     };
2716
2717     var sencha = Ext.Object.merge(extjs, newStuff);
2718
2719     // extjs and sencha then equals to
2720     {
2721         companyName: 'Sencha Inc.',
2722         products: ['Ext JS', 'Ext GWT', 'Ext Designer', 'Sencha Touch', 'Sencha Animator'],
2723         isSuperCool: true
2724         office: {
2725             size: 30000,
2726             location: 'Redwood City'
2727             isFun: true
2728         }
2729     }
2730
2731      * @param {Object} object,...
2732      * @return {Object} merged The object that is created as a result of merging all the objects passed in.
2733      * @markdown
2734      */
2735     merge: function(source, key, value) {
2736         if (typeof key === 'string') {
2737             if (value && value.constructor === Object) {
2738                 if (source[key] && source[key].constructor === Object) {
2739                     ExtObject.merge(source[key], value);
2740                 }
2741                 else {
2742                     source[key] = Ext.clone(value);
2743                 }
2744             }
2745             else {
2746                 source[key] = value;
2747             }
2748
2749             return source;
2750         }
2751
2752         var i = 1,
2753             ln = arguments.length,
2754             object, property;
2755
2756         for (; i < ln; i++) {
2757             object = arguments[i];
2758
2759             for (property in object) {
2760                 if (object.hasOwnProperty(property)) {
2761                     ExtObject.merge(source, property, object[property]);
2762                 }
2763             }
2764         }
2765
2766         return source;
2767     },
2768
2769     /**
2770      * Returns the first matching key corresponding to the given value.
2771      * If no matching value is found, null is returned.
2772
2773     var person = {
2774         name: 'Jacky',
2775         loves: 'food'
2776     };
2777
2778     alert(Ext.Object.getKey(sencha, 'loves')); // alerts 'food'
2779
2780      * @param {Object} object
2781      * @param {Object} value The value to find
2782      * @markdown
2783      */
2784     getKey: function(object, value) {
2785         for (var property in object) {
2786             if (object.hasOwnProperty(property) && object[property] === value) {
2787                 return property;
2788             }
2789         }
2790
2791         return null;
2792     },
2793
2794     /**
2795      * Gets all values of the given object as an array.
2796
2797     var values = Ext.Object.getValues({
2798         name: 'Jacky',
2799         loves: 'food'
2800     }); // ['Jacky', 'food']
2801
2802      * @param {Object} object
2803      * @return {Array} An array of values from the object
2804      * @markdown
2805      */
2806     getValues: function(object) {
2807         var values = [],
2808             property;
2809
2810         for (property in object) {
2811             if (object.hasOwnProperty(property)) {
2812                 values.push(object[property]);
2813             }
2814         }
2815
2816         return values;
2817     },
2818
2819     /**
2820      * Gets all keys of the given object as an array.
2821
2822     var values = Ext.Object.getKeys({
2823         name: 'Jacky',
2824         loves: 'food'
2825     }); // ['name', 'loves']
2826
2827      * @param {Object} object
2828      * @return {Array} An array of keys from the object
2829      */
2830     getKeys: ('keys' in Object.prototype) ? Object.keys : function(object) {
2831         var keys = [],
2832             property;
2833
2834         for (property in object) {
2835             if (object.hasOwnProperty(property)) {
2836                 keys.push(property);
2837             }
2838         }
2839
2840         return keys;
2841     },
2842
2843     /**
2844      * Gets the total number of this object's own properties
2845
2846     var size = Ext.Object.getSize({
2847         name: 'Jacky',
2848         loves: 'food'
2849     }); // size equals 2
2850
2851      * @param {Object} object
2852      * @return {Number} size
2853      * @markdown
2854      */
2855     getSize: function(object) {
2856         var size = 0,
2857             property;
2858
2859         for (property in object) {
2860             if (object.hasOwnProperty(property)) {
2861                 size++;
2862             }
2863         }
2864
2865         return size;
2866     }
2867 };
2868
2869
2870 /**
2871  * A convenient alias method for {@link Ext.Object#merge}
2872  *
2873  * @member Ext
2874  * @method merge
2875  */
2876 Ext.merge = Ext.Object.merge;
2877
2878 /**
2879  * A convenient alias method for {@link Ext.Object#toQueryString}
2880  *
2881  * @member Ext
2882  * @method urlEncode
2883  * @deprecated 4.0.0 Use {@link Ext.Object#toQueryString Ext.Object.toQueryString} instead
2884  */
2885 Ext.urlEncode = function() {
2886     var args = Ext.Array.from(arguments),
2887         prefix = '';
2888
2889     // Support for the old `pre` argument
2890     if ((typeof args[1] === 'string')) {
2891         prefix = args[1] + '&';
2892         args[1] = false;
2893     }
2894
2895     return prefix + Ext.Object.toQueryString.apply(Ext.Object, args);
2896 };
2897
2898 /**
2899  * A convenient alias method for {@link Ext.Object#fromQueryString}
2900  *
2901  * @member Ext
2902  * @method urlDecode
2903  * @deprecated 4.0.0 Use {@link Ext.Object#fromQueryString Ext.Object.fromQueryString} instead
2904  */
2905 Ext.urlDecode = function() {
2906     return Ext.Object.fromQueryString.apply(Ext.Object, arguments);
2907 };
2908
2909 })();
2910
2911 /**
2912  * @class Ext.Date
2913  * A set of useful static methods to deal with date
2914  * Note that if Ext.Date is required and loaded, it will copy all methods / properties to
2915  * this object for convenience
2916  *
2917  * The date parsing and formatting syntax contains a subset of
2918  * <a href="http://www.php.net/date">PHP's date() function</a>, and the formats that are
2919  * supported will provide results equivalent to their PHP versions.
2920  *
2921  * The following is a list of all currently supported formats:
2922  * <pre class="">
2923 Format  Description                                                               Example returned values
2924 ------  -----------------------------------------------------------------------   -----------------------
2925   d     Day of the month, 2 digits with leading zeros                             01 to 31
2926   D     A short textual representation of the day of the week                     Mon to Sun
2927   j     Day of the month without leading zeros                                    1 to 31
2928   l     A full textual representation of the day of the week                      Sunday to Saturday
2929   N     ISO-8601 numeric representation of the day of the week                    1 (for Monday) through 7 (for Sunday)
2930   S     English ordinal suffix for the day of the month, 2 characters             st, nd, rd or th. Works well with j
2931   w     Numeric representation of the day of the week                             0 (for Sunday) to 6 (for Saturday)
2932   z     The day of the year (starting from 0)                                     0 to 364 (365 in leap years)
2933   W     ISO-8601 week number of year, weeks starting on Monday                    01 to 53
2934   F     A full textual representation of a month, such as January or March        January to December
2935   m     Numeric representation of a month, with leading zeros                     01 to 12
2936   M     A short textual representation of a month                                 Jan to Dec
2937   n     Numeric representation of a month, without leading zeros                  1 to 12
2938   t     Number of days in the given month                                         28 to 31
2939   L     Whether it&#39;s a leap year                                                  1 if it is a leap year, 0 otherwise.
2940   o     ISO-8601 year number (identical to (Y), but if the ISO week number (W)    Examples: 1998 or 2004
2941         belongs to the previous or next year, that year is used instead)
2942   Y     A full numeric representation of a year, 4 digits                         Examples: 1999 or 2003
2943   y     A two digit representation of a year                                      Examples: 99 or 03
2944   a     Lowercase Ante meridiem and Post meridiem                                 am or pm
2945   A     Uppercase Ante meridiem and Post meridiem                                 AM or PM
2946   g     12-hour format of an hour without leading zeros                           1 to 12
2947   G     24-hour format of an hour without leading zeros                           0 to 23
2948   h     12-hour format of an hour with leading zeros                              01 to 12
2949   H     24-hour format of an hour with leading zeros                              00 to 23
2950   i     Minutes, with leading zeros                                               00 to 59
2951   s     Seconds, with leading zeros                                               00 to 59
2952   u     Decimal fraction of a second                                              Examples:
2953         (minimum 1 digit, arbitrary number of digits allowed)                     001 (i.e. 0.001s) or
2954                                                                                   100 (i.e. 0.100s) or
2955                                                                                   999 (i.e. 0.999s) or
2956                                                                                   999876543210 (i.e. 0.999876543210s)
2957   O     Difference to Greenwich time (GMT) in hours and minutes                   Example: +1030
2958   P     Difference to Greenwich time (GMT) with colon between hours and minutes   Example: -08:00
2959   T     Timezone abbreviation of the machine running the code                     Examples: EST, MDT, PDT ...
2960   Z     Timezone offset in seconds (negative if west of UTC, positive if east)    -43200 to 50400
2961   c     ISO 8601 date
2962         Notes:                                                                    Examples:
2963         1) If unspecified, the month / day defaults to the current month / day,   1991 or
2964            the time defaults to midnight, while the timezone defaults to the      1992-10 or
2965            browser's timezone. If a time is specified, it must include both hours 1993-09-20 or
2966            and minutes. The "T" delimiter, seconds, milliseconds and timezone     1994-08-19T16:20+01:00 or
2967            are optional.                                                          1995-07-18T17:21:28-02:00 or
2968         2) The decimal fraction of a second, if specified, must contain at        1996-06-17T18:22:29.98765+03:00 or
2969            least 1 digit (there is no limit to the maximum number                 1997-05-16T19:23:30,12345-0400 or
2970            of digits allowed), and may be delimited by either a '.' or a ','      1998-04-15T20:24:31.2468Z or
2971         Refer to the examples on the right for the various levels of              1999-03-14T20:24:32Z or
2972         date-time granularity which are supported, or see                         2000-02-13T21:25:33
2973         http://www.w3.org/TR/NOTE-datetime for more info.                         2001-01-12 22:26:34
2974   U     Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT)                1193432466 or -2138434463
2975   MS    Microsoft AJAX serialized dates                                           \/Date(1238606590509)\/ (i.e. UTC milliseconds since epoch) or
2976                                                                                   \/Date(1238606590509+0800)\/
2977 </pre>
2978  *
2979  * Example usage (note that you must escape format specifiers with '\\' to render them as character literals):
2980  * <pre><code>
2981 // Sample date:
2982 // 'Wed Jan 10 2007 15:05:01 GMT-0600 (Central Standard Time)'
2983
2984 var dt = new Date('1/10/2007 03:05:01 PM GMT-0600');
2985 console.log(Ext.Date.format(dt, 'Y-m-d'));                          // 2007-01-10
2986 console.log(Ext.Date.format(dt, 'F j, Y, g:i a'));                  // January 10, 2007, 3:05 pm
2987 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
2988 </code></pre>
2989  *
2990  * Here are some standard date/time patterns that you might find helpful.  They
2991  * are not part of the source of Ext.Date, but to use them you can simply copy this
2992  * block of code into any script that is included after Ext.Date and they will also become
2993  * globally available on the Date object.  Feel free to add or remove patterns as needed in your code.
2994  * <pre><code>
2995 Ext.Date.patterns = {
2996     ISO8601Long:"Y-m-d H:i:s",
2997     ISO8601Short:"Y-m-d",
2998     ShortDate: "n/j/Y",
2999     LongDate: "l, F d, Y",
3000     FullDateTime: "l, F d, Y g:i:s A",
3001     MonthDay: "F d",
3002     ShortTime: "g:i A",
3003     LongTime: "g:i:s A",
3004     SortableDateTime: "Y-m-d\\TH:i:s",
3005     UniversalSortableDateTime: "Y-m-d H:i:sO",
3006     YearMonth: "F, Y"
3007 };
3008 </code></pre>
3009  *
3010  * Example usage:
3011  * <pre><code>
3012 var dt = new Date();
3013 console.log(Ext.Date.format(dt, Ext.Date.patterns.ShortDate));
3014 </code></pre>
3015  * <p>Developer-written, custom formats may be used by supplying both a formatting and a parsing function
3016  * which perform to specialized requirements. The functions are stored in {@link #parseFunctions} and {@link #formatFunctions}.</p>
3017  * @singleton
3018  */
3019
3020 /*
3021  * Most of the date-formatting functions below are the excellent work of Baron Schwartz.
3022  * (see http://www.xaprb.com/blog/2005/12/12/javascript-closures-for-runtime-efficiency/)
3023  * They generate precompiled functions from format patterns instead of parsing and
3024  * processing each pattern every time a date is formatted. These functions are available
3025  * on every Date object.
3026  */
3027
3028 (function() {
3029
3030 // create private copy of Ext's Ext.util.Format.format() method
3031 // - to remove unnecessary dependency
3032 // - to resolve namespace conflict with MS-Ajax's implementation
3033 function xf(format) {
3034     var args = Array.prototype.slice.call(arguments, 1);
3035     return format.replace(/\{(\d+)\}/g, function(m, i) {
3036         return args[i];
3037     });
3038 }
3039
3040 Ext.Date = {
3041     /**
3042      * Returns the current timestamp
3043      * @return {Date} The current timestamp
3044      */
3045     now: Date.now || function() {
3046         return +new Date();
3047     },
3048
3049     /**
3050      * @private
3051      * Private for now
3052      */
3053     toString: function(date) {
3054         var pad = Ext.String.leftPad;
3055
3056         return date.getFullYear() + "-"
3057             + pad(date.getMonth() + 1, 2, '0') + "-"
3058             + pad(date.getDate(), 2, '0') + "T"
3059             + pad(date.getHours(), 2, '0') + ":"
3060             + pad(date.getMinutes(), 2, '0') + ":"
3061             + pad(date.getSeconds(), 2, '0');
3062     },
3063
3064     /**
3065      * Returns the number of milliseconds between two dates
3066      * @param {Date} dateA The first date
3067      * @param {Date} dateB (optional) The second date, defaults to now
3068      * @return {Number} The difference in milliseconds
3069      */
3070     getElapsed: function(dateA, dateB) {
3071         return Math.abs(dateA - (dateB || new Date()));
3072     },
3073
3074     /**
3075      * Global flag which determines if strict date parsing should be used.
3076      * Strict date parsing will not roll-over invalid dates, which is the
3077      * default behaviour of javascript Date objects.
3078      * (see {@link #parse} for more information)
3079      * Defaults to <tt>false</tt>.
3080      * @static
3081      * @type Boolean
3082     */
3083     useStrict: false,
3084
3085     // private
3086     formatCodeToRegex: function(character, currentGroup) {
3087         // Note: currentGroup - position in regex result array (see notes for Ext.Date.parseCodes below)
3088         var p = utilDate.parseCodes[character];
3089
3090         if (p) {
3091           p = typeof p == 'function'? p() : p;
3092           utilDate.parseCodes[character] = p; // reassign function result to prevent repeated execution
3093         }
3094
3095         return p ? Ext.applyIf({
3096           c: p.c ? xf(p.c, currentGroup || "{0}") : p.c
3097         }, p) : {
3098             g: 0,
3099             c: null,
3100             s: Ext.String.escapeRegex(character) // treat unrecognised characters as literals
3101         };
3102     },
3103
3104     /**
3105      * <p>An object hash in which each property is a date parsing function. The property name is the
3106      * format string which that function parses.</p>
3107      * <p>This object is automatically populated with date parsing functions as
3108      * date formats are requested for Ext standard formatting strings.</p>
3109      * <p>Custom parsing functions may be inserted into this object, keyed by a name which from then on
3110      * may be used as a format string to {@link #parse}.<p>
3111      * <p>Example:</p><pre><code>
3112 Ext.Date.parseFunctions['x-date-format'] = myDateParser;
3113 </code></pre>
3114      * <p>A parsing function should return a Date object, and is passed the following parameters:<div class="mdetail-params"><ul>
3115      * <li><code>date</code> : String<div class="sub-desc">The date string to parse.</div></li>
3116      * <li><code>strict</code> : Boolean<div class="sub-desc">True to validate date strings while parsing
3117      * (i.e. prevent javascript Date "rollover") (The default must be false).
3118      * Invalid date strings should return null when parsed.</div></li>
3119      * </ul></div></p>
3120      * <p>To enable Dates to also be <i>formatted</i> according to that format, a corresponding
3121      * formatting function must be placed into the {@link #formatFunctions} property.
3122      * @property parseFunctions
3123      * @static
3124      * @type Object
3125      */
3126     parseFunctions: {
3127         "MS": function(input, strict) {
3128             // note: the timezone offset is ignored since the MS Ajax server sends
3129             // a UTC milliseconds-since-Unix-epoch value (negative values are allowed)
3130             var re = new RegExp('\\/Date\\(([-+])?(\\d+)(?:[+-]\\d{4})?\\)\\/');
3131             var r = (input || '').match(re);
3132             return r? new Date(((r[1] || '') + r[2]) * 1) : null;
3133         }
3134     },
3135     parseRegexes: [],
3136
3137     /**
3138      * <p>An object hash in which each property is a date formatting function. The property name is the
3139      * format string which corresponds to the produced formatted date string.</p>
3140      * <p>This object is automatically populated with date formatting functions as
3141      * date formats are requested for Ext standard formatting strings.</p>
3142      * <p>Custom formatting functions may be inserted into this object, keyed by a name which from then on
3143      * may be used as a format string to {@link #format}. Example:</p><pre><code>
3144 Ext.Date.formatFunctions['x-date-format'] = myDateFormatter;
3145 </code></pre>
3146      * <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>
3147      * <li><code>date</code> : Date<div class="sub-desc">The Date to format.</div></li>
3148      * </ul></div></p>
3149      * <p>To enable date strings to also be <i>parsed</i> according to that format, a corresponding
3150      * parsing function must be placed into the {@link #parseFunctions} property.
3151      * @property formatFunctions
3152      * @static
3153      * @type Object
3154      */
3155     formatFunctions: {
3156         "MS": function() {
3157             // UTC milliseconds since Unix epoch (MS-AJAX serialized date format (MRSF))
3158             return '\\/Date(' + this.getTime() + ')\\/';
3159         }
3160     },
3161
3162     y2kYear : 50,
3163
3164     /**
3165      * Date interval constant
3166      * @static
3167      * @type String
3168      */
3169     MILLI : "ms",
3170
3171     /**
3172      * Date interval constant
3173      * @static
3174      * @type String
3175      */
3176     SECOND : "s",
3177
3178     /**
3179      * Date interval constant
3180      * @static
3181      * @type String
3182      */
3183     MINUTE : "mi",
3184
3185     /** Date interval constant
3186      * @static
3187      * @type String
3188      */
3189     HOUR : "h",
3190
3191     /**
3192      * Date interval constant
3193      * @static
3194      * @type String
3195      */
3196     DAY : "d",
3197
3198     /**
3199      * Date interval constant
3200      * @static
3201      * @type String
3202      */
3203     MONTH : "mo",
3204
3205     /**
3206      * Date interval constant
3207      * @static
3208      * @type String
3209      */
3210     YEAR : "y",
3211
3212     /**
3213      * <p>An object hash containing default date values used during date parsing.</p>
3214      * <p>The following properties are available:<div class="mdetail-params"><ul>
3215      * <li><code>y</code> : Number<div class="sub-desc">The default year value. (defaults to undefined)</div></li>
3216      * <li><code>m</code> : Number<div class="sub-desc">The default 1-based month value. (defaults to undefined)</div></li>
3217      * <li><code>d</code> : Number<div class="sub-desc">The default day value. (defaults to undefined)</div></li>
3218      * <li><code>h</code> : Number<div class="sub-desc">The default hour value. (defaults to undefined)</div></li>
3219      * <li><code>i</code> : Number<div class="sub-desc">The default minute value. (defaults to undefined)</div></li>
3220      * <li><code>s</code> : Number<div class="sub-desc">The default second value. (defaults to undefined)</div></li>
3221      * <li><code>ms</code> : Number<div class="sub-desc">The default millisecond value. (defaults to undefined)</div></li>
3222      * </ul></div></p>
3223      * <p>Override these properties to customize the default date values used by the {@link #parse} method.</p>
3224      * <p><b>Note: In countries which experience Daylight Saving Time (i.e. DST), the <tt>h</tt>, <tt>i</tt>, <tt>s</tt>
3225      * and <tt>ms</tt> properties may coincide with the exact time in which DST takes effect.
3226      * It is the responsiblity of the developer to account for this.</b></p>
3227      * Example Usage:
3228      * <pre><code>
3229 // set default day value to the first day of the month
3230 Ext.Date.defaults.d = 1;
3231
3232 // parse a February date string containing only year and month values.
3233 // setting the default day value to 1 prevents weird date rollover issues
3234 // when attempting to parse the following date string on, for example, March 31st 2009.
3235 Ext.Date.parse('2009-02', 'Y-m'); // returns a Date object representing February 1st 2009
3236 </code></pre>
3237      * @property defaults
3238      * @static
3239      * @type Object
3240      */
3241     defaults: {},
3242
3243     /**
3244      * An array of textual day names.
3245      * Override these values for international dates.
3246      * Example:
3247      * <pre><code>
3248 Ext.Date.dayNames = [
3249     'SundayInYourLang',
3250     'MondayInYourLang',
3251     ...
3252 ];
3253 </code></pre>
3254      * @type Array
3255      * @static
3256      */
3257     dayNames : [
3258         "Sunday",
3259         "Monday",
3260         "Tuesday",
3261         "Wednesday",
3262         "Thursday",
3263         "Friday",
3264         "Saturday"
3265     ],
3266
3267     /**
3268      * An array of textual month names.
3269      * Override these values for international dates.
3270      * Example:
3271      * <pre><code>
3272 Ext.Date.monthNames = [
3273     'JanInYourLang',
3274     'FebInYourLang',
3275     ...
3276 ];
3277 </code></pre>
3278      * @type Array
3279      * @static
3280      */
3281     monthNames : [
3282         "January",
3283         "February",
3284         "March",
3285         "April",
3286         "May",
3287         "June",
3288         "July",
3289         "August",
3290         "September",
3291         "October",
3292         "November",
3293         "December"
3294     ],
3295
3296     /**
3297      * An object hash of zero-based javascript month numbers (with short month names as keys. note: keys are case-sensitive).
3298      * Override these values for international dates.
3299      * Example:
3300      * <pre><code>
3301 Ext.Date.monthNumbers = {
3302     'ShortJanNameInYourLang':0,
3303     'ShortFebNameInYourLang':1,
3304     ...
3305 };
3306 </code></pre>
3307      * @type Object
3308      * @static
3309      */
3310     monthNumbers : {
3311         Jan:0,
3312         Feb:1,
3313         Mar:2,
3314         Apr:3,
3315         May:4,
3316         Jun:5,
3317         Jul:6,
3318         Aug:7,
3319         Sep:8,
3320         Oct:9,
3321         Nov:10,
3322         Dec:11
3323     },
3324     /**
3325      * <p>The date format string that the {@link #dateRenderer} and {@link #date} functions use.
3326      * see {@link #Date} for details.</p>
3327      * <p>This defaults to <code>m/d/Y</code>, but may be overridden in a locale file.</p>
3328      * @property defaultFormat
3329      * @static
3330      * @type String
3331      */
3332     defaultFormat : "m/d/Y",
3333     /**
3334      * Get the short month name for the given month number.
3335      * Override this function for international dates.
3336      * @param {Number} month A zero-based javascript month number.
3337      * @return {String} The short month name.
3338      * @static
3339      */
3340     getShortMonthName : function(month) {
3341         return utilDate.monthNames[month].substring(0, 3);
3342     },
3343
3344     /**
3345      * Get the short day name for the given day number.
3346      * Override this function for international dates.
3347      * @param {Number} day A zero-based javascript day number.
3348      * @return {String} The short day name.
3349      * @static
3350      */
3351     getShortDayName : function(day) {
3352         return utilDate.dayNames[day].substring(0, 3);
3353     },
3354
3355     /**
3356      * Get the zero-based javascript month number for the given short/full month name.
3357      * Override this function for international dates.
3358      * @param {String} name The short/full month name.
3359      * @return {Number} The zero-based javascript month number.
3360      * @static
3361      */
3362     getMonthNumber : function(name) {
3363         // handle camel casing for english month names (since the keys for the Ext.Date.monthNumbers hash are case sensitive)
3364         return utilDate.monthNumbers[name.substring(0, 1).toUpperCase() + name.substring(1, 3).toLowerCase()];
3365     },
3366
3367     /**
3368      * Checks if the specified format contains hour information
3369      * @param {String} format The format to check
3370      * @return {Boolean} True if the format contains hour information
3371      * @static
3372      */
3373     formatContainsHourInfo : (function(){
3374         var stripEscapeRe = /(\\.)/g,
3375             hourInfoRe = /([gGhHisucUOPZ]|MS)/;
3376         return function(format){
3377             return hourInfoRe.test(format.replace(stripEscapeRe, ''));
3378         };
3379     })(),
3380
3381     /**
3382      * Checks if the specified format contains information about
3383      * anything other than the time.
3384      * @param {String} format The format to check
3385      * @return {Boolean} True if the format contains information about
3386      * date/day information.
3387      * @static
3388      */
3389     formatContainsDateInfo : (function(){
3390         var stripEscapeRe = /(\\.)/g,
3391             dateInfoRe = /([djzmnYycU]|MS)/;
3392
3393         return function(format){
3394             return dateInfoRe.test(format.replace(stripEscapeRe, ''));
3395         };
3396     })(),
3397
3398     /**
3399      * The base format-code to formatting-function hashmap used by the {@link #format} method.
3400      * Formatting functions are strings (or functions which return strings) which
3401      * will return the appropriate value when evaluated in the context of the Date object
3402      * from which the {@link #format} method is called.
3403      * Add to / override these mappings for custom date formatting.
3404      * Note: Ext.Date.format() treats characters as literals if an appropriate mapping cannot be found.
3405      * Example:
3406      * <pre><code>
3407 Ext.Date.formatCodes.x = "Ext.util.Format.leftPad(this.getDate(), 2, '0')";
3408 console.log(Ext.Date.format(new Date(), 'X'); // returns the current day of the month
3409 </code></pre>
3410      * @type Object
3411      * @static
3412      */
3413     formatCodes : {
3414         d: "Ext.String.leftPad(this.getDate(), 2, '0')",
3415         D: "Ext.Date.getShortDayName(this.getDay())", // get localised short day name
3416         j: "this.getDate()",
3417         l: "Ext.Date.dayNames[this.getDay()]",
3418         N: "(this.getDay() ? this.getDay() : 7)",
3419         S: "Ext.Date.getSuffix(this)",
3420         w: "this.getDay()",
3421         z: "Ext.Date.getDayOfYear(this)",
3422         W: "Ext.String.leftPad(Ext.Date.getWeekOfYear(this), 2, '0')",
3423         F: "Ext.Date.monthNames[this.getMonth()]",
3424         m: "Ext.String.leftPad(this.getMonth() + 1, 2, '0')",
3425         M: "Ext.Date.getShortMonthName(this.getMonth())", // get localised short month name
3426         n: "(this.getMonth() + 1)",
3427         t: "Ext.Date.getDaysInMonth(this)",
3428         L: "(Ext.Date.isLeapYear(this) ? 1 : 0)",
3429         o: "(this.getFullYear() + (Ext.Date.getWeekOfYear(this) == 1 && this.getMonth() > 0 ? +1 : (Ext.Date.getWeekOfYear(this) >= 52 && this.getMonth() < 11 ? -1 : 0)))",
3430         Y: "Ext.String.leftPad(this.getFullYear(), 4, '0')",
3431         y: "('' + this.getFullYear()).substring(2, 4)",
3432         a: "(this.getHours() < 12 ? 'am' : 'pm')",
3433         A: "(this.getHours() < 12 ? 'AM' : 'PM')",
3434         g: "((this.getHours() % 12) ? this.getHours() % 12 : 12)",
3435         G: "this.getHours()",
3436         h: "Ext.String.leftPad((this.getHours() % 12) ? this.getHours() % 12 : 12, 2, '0')",
3437         H: "Ext.String.leftPad(this.getHours(), 2, '0')",
3438         i: "Ext.String.leftPad(this.getMinutes(), 2, '0')",
3439         s: "Ext.String.leftPad(this.getSeconds(), 2, '0')",
3440         u: "Ext.String.leftPad(this.getMilliseconds(), 3, '0')",
3441         O: "Ext.Date.getGMTOffset(this)",
3442         P: "Ext.Date.getGMTOffset(this, true)",
3443         T: "Ext.Date.getTimezone(this)",
3444         Z: "(this.getTimezoneOffset() * -60)",
3445
3446         c: function() { // ISO-8601 -- GMT format
3447             for (var c = "Y-m-dTH:i:sP", code = [], i = 0, l = c.length; i < l; ++i) {
3448                 var e = c.charAt(i);
3449                 code.push(e == "T" ? "'T'" : utilDate.getFormatCode(e)); // treat T as a character literal
3450             }
3451             return code.join(" + ");
3452         },
3453         /*
3454         c: function() { // ISO-8601 -- UTC format
3455             return [
3456               "this.getUTCFullYear()", "'-'",
3457               "Ext.util.Format.leftPad(this.getUTCMonth() + 1, 2, '0')", "'-'",
3458               "Ext.util.Format.leftPad(this.getUTCDate(), 2, '0')",
3459               "'T'",
3460               "Ext.util.Format.leftPad(this.getUTCHours(), 2, '0')", "':'",
3461               "Ext.util.Format.leftPad(this.getUTCMinutes(), 2, '0')", "':'",
3462               "Ext.util.Format.leftPad(this.getUTCSeconds(), 2, '0')",
3463               "'Z'"
3464             ].join(" + ");
3465         },
3466         */
3467
3468         U: "Math.round(this.getTime() / 1000)"
3469     },
3470
3471     /**
3472      * Checks if the passed Date parameters will cause a javascript Date "rollover".
3473      * @param {Number} year 4-digit year
3474      * @param {Number} month 1-based month-of-year
3475      * @param {Number} day Day of month
3476      * @param {Number} hour (optional) Hour
3477      * @param {Number} minute (optional) Minute
3478      * @param {Number} second (optional) Second
3479      * @param {Number} millisecond (optional) Millisecond
3480      * @return {Boolean} true if the passed parameters do not cause a Date "rollover", false otherwise.
3481      * @static
3482      */
3483     isValid : function(y, m, d, h, i, s, ms) {
3484         // setup defaults
3485         h = h || 0;
3486         i = i || 0;
3487         s = s || 0;
3488         ms = ms || 0;
3489
3490         // Special handling for year < 100
3491         var dt = utilDate.add(new Date(y < 100 ? 100 : y, m - 1, d, h, i, s, ms), utilDate.YEAR, y < 100 ? y - 100 : 0);
3492
3493         return y == dt.getFullYear() &&
3494             m == dt.getMonth() + 1 &&
3495             d == dt.getDate() &&
3496             h == dt.getHours() &&
3497             i == dt.getMinutes() &&
3498             s == dt.getSeconds() &&
3499             ms == dt.getMilliseconds();
3500     },
3501
3502     /**
3503      * Parses the passed string using the specified date format.
3504      * Note that this function expects normal calendar dates, meaning that months are 1-based (i.e. 1 = January).
3505      * The {@link #defaults} hash will be used for any date value (i.e. year, month, day, hour, minute, second or millisecond)
3506      * which cannot be found in the passed string. If a corresponding default date value has not been specified in the {@link #defaults} hash,
3507      * the current date's year, month, day or DST-adjusted zero-hour time value will be used instead.
3508      * Keep in mind that the input date string must precisely match the specified format string
3509      * in order for the parse operation to be successful (failed parse operations return a null value).
3510      * <p>Example:</p><pre><code>
3511 //dt = Fri May 25 2007 (current date)
3512 var dt = new Date();
3513
3514 //dt = Thu May 25 2006 (today&#39;s month/day in 2006)
3515 dt = Ext.Date.parse("2006", "Y");
3516
3517 //dt = Sun Jan 15 2006 (all date parts specified)
3518 dt = Ext.Date.parse("2006-01-15", "Y-m-d");
3519
3520 //dt = Sun Jan 15 2006 15:20:01
3521 dt = Ext.Date.parse("2006-01-15 3:20:01 PM", "Y-m-d g:i:s A");
3522
3523 // attempt to parse Sun Feb 29 2006 03:20:01 in strict mode
3524 dt = Ext.Date.parse("2006-02-29 03:20:01", "Y-m-d H:i:s", true); // returns null
3525 </code></pre>
3526      * @param {String} input The raw date string.
3527      * @param {String} format The expected date string format.
3528      * @param {Boolean} strict (optional) True to validate date strings while parsing (i.e. prevents javascript Date "rollover")
3529                         (defaults to false). Invalid date strings will return null when parsed.
3530      * @return {Date} The parsed Date.
3531      * @static
3532      */
3533     parse : function(input, format, strict) {
3534         var p = utilDate.parseFunctions;
3535         if (p[format] == null) {
3536             utilDate.createParser(format);
3537         }
3538         return p[format](input, Ext.isDefined(strict) ? strict : utilDate.useStrict);
3539     },
3540
3541     // Backwards compat
3542     parseDate: function(input, format, strict){
3543         return utilDate.parse(input, format, strict);
3544     },
3545
3546
3547     // private
3548     getFormatCode : function(character) {
3549         var f = utilDate.formatCodes[character];
3550
3551         if (f) {
3552           f = typeof f == 'function'? f() : f;
3553           utilDate.formatCodes[character] = f; // reassign function result to prevent repeated execution
3554         }
3555
3556         // note: unknown characters are treated as literals
3557         return f || ("'" + Ext.String.escape(character) + "'");
3558     },
3559
3560     // private
3561     createFormat : function(format) {
3562         var code = [],
3563             special = false,
3564             ch = '';
3565
3566         for (var i = 0; i < format.length; ++i) {
3567             ch = format.charAt(i);
3568             if (!special && ch == "\\") {
3569                 special = true;
3570             } else if (special) {
3571                 special = false;
3572                 code.push("'" + Ext.String.escape(ch) + "'");
3573             } else {
3574                 code.push(utilDate.getFormatCode(ch));
3575             }
3576         }
3577         utilDate.formatFunctions[format] = Ext.functionFactory("return " + code.join('+'));
3578     },
3579
3580     // private
3581     createParser : (function() {
3582         var code = [
3583             "var dt, y, m, d, h, i, s, ms, o, z, zz, u, v,",
3584                 "def = Ext.Date.defaults,",
3585                 "results = String(input).match(Ext.Date.parseRegexes[{0}]);", // either null, or an array of matched strings
3586
3587             "if(results){",
3588                 "{1}",
3589
3590                 "if(u != null){", // i.e. unix time is defined
3591                     "v = new Date(u * 1000);", // give top priority to UNIX time
3592                 "}else{",
3593                     // create Date object representing midnight of the current day;
3594                     // this will provide us with our date defaults
3595                     // (note: clearTime() handles Daylight Saving Time automatically)
3596                     "dt = Ext.Date.clearTime(new Date);",
3597
3598                     // date calculations (note: these calculations create a dependency on Ext.Number.from())
3599                     "y = Ext.Number.from(y, Ext.Number.from(def.y, dt.getFullYear()));",
3600                     "m = Ext.Number.from(m, Ext.Number.from(def.m - 1, dt.getMonth()));",
3601                     "d = Ext.Number.from(d, Ext.Number.from(def.d, dt.getDate()));",
3602
3603                     // time calculations (note: these calculations create a dependency on Ext.Number.from())
3604                     "h  = Ext.Number.from(h, Ext.Number.from(def.h, dt.getHours()));",
3605                     "i  = Ext.Number.from(i, Ext.Number.from(def.i, dt.getMinutes()));",
3606                     "s  = Ext.Number.from(s, Ext.Number.from(def.s, dt.getSeconds()));",
3607                     "ms = Ext.Number.from(ms, Ext.Number.from(def.ms, dt.getMilliseconds()));",
3608
3609                     "if(z >= 0 && y >= 0){",
3610                         // both the year and zero-based day of year are defined and >= 0.
3611                         // these 2 values alone provide sufficient info to create a full date object
3612
3613                         // create Date object representing January 1st for the given year
3614                         // handle years < 100 appropriately
3615                         "v = Ext.Date.add(new Date(y < 100 ? 100 : y, 0, 1, h, i, s, ms), Ext.Date.YEAR, y < 100 ? y - 100 : 0);",
3616
3617                         // then add day of year, checking for Date "rollover" if necessary
3618                         "v = !strict? v : (strict === true && (z <= 364 || (Ext.Date.isLeapYear(v) && z <= 365))? Ext.Date.add(v, Ext.Date.DAY, z) : null);",
3619                     "}else if(strict === true && !Ext.Date.isValid(y, m + 1, d, h, i, s, ms)){", // check for Date "rollover"
3620                         "v = null;", // invalid date, so return null
3621                     "}else{",
3622                         // plain old Date object
3623                         // handle years < 100 properly
3624                         "v = Ext.Date.add(new Date(y < 100 ? 100 : y, m, d, h, i, s, ms), Ext.Date.YEAR, y < 100 ? y - 100 : 0);",
3625                     "}",
3626                 "}",
3627             "}",
3628
3629             "if(v){",
3630                 // favour UTC offset over GMT offset
3631                 "if(zz != null){",
3632                     // reset to UTC, then add offset
3633                     "v = Ext.Date.add(v, Ext.Date.SECOND, -v.getTimezoneOffset() * 60 - zz);",
3634                 "}else if(o){",
3635                     // reset to GMT, then add offset
3636                     "v = Ext.Date.add(v, Ext.Date.MINUTE, -v.getTimezoneOffset() + (sn == '+'? -1 : 1) * (hr * 60 + mn));",
3637                 "}",
3638             "}",
3639
3640             "return v;"
3641         ].join('\n');
3642
3643         return function(format) {
3644             var regexNum = utilDate.parseRegexes.length,
3645                 currentGroup = 1,
3646                 calc = [],
3647                 regex = [],
3648                 special = false,
3649                 ch = "";
3650
3651             for (var i = 0; i < format.length; ++i) {
3652                 ch = format.charAt(i);
3653                 if (!special && ch == "\\") {
3654                     special = true;
3655                 } else if (special) {
3656                     special = false;
3657                     regex.push(Ext.String.escape(ch));
3658                 } else {
3659                     var obj = utilDate.formatCodeToRegex(ch, currentGroup);
3660                     currentGroup += obj.g;
3661                     regex.push(obj.s);
3662                     if (obj.g && obj.c) {
3663                         calc.push(obj.c);
3664                     }
3665                 }
3666             }
3667
3668             utilDate.parseRegexes[regexNum] = new RegExp("^" + regex.join('') + "$", 'i');
3669             utilDate.parseFunctions[format] = Ext.functionFactory("input", "strict", xf(code, regexNum, calc.join('')));
3670         };
3671     })(),
3672
3673     // private
3674     parseCodes : {
3675         /*
3676          * Notes:
3677          * g = {Number} calculation group (0 or 1. only group 1 contributes to date calculations.)
3678          * c = {String} calculation method (required for group 1. null for group 0. {0} = currentGroup - position in regex result array)
3679          * s = {String} regex pattern. all matches are stored in results[], and are accessible by the calculation mapped to 'c'
3680          */
3681         d: {
3682             g:1,
3683             c:"d = parseInt(results[{0}], 10);\n",
3684             s:"(\\d{2})" // day of month with leading zeroes (01 - 31)
3685         },
3686         j: {
3687             g:1,
3688             c:"d = parseInt(results[{0}], 10);\n",
3689             s:"(\\d{1,2})" // day of month without leading zeroes (1 - 31)
3690         },
3691         D: function() {
3692             for (var a = [], i = 0; i < 7; a.push(utilDate.getShortDayName(i)), ++i); // get localised short day names
3693             return {
3694                 g:0,
3695                 c:null,
3696                 s:"(?:" + a.join("|") +")"
3697             };
3698         },
3699         l: function() {
3700             return {
3701                 g:0,
3702                 c:null,
3703                 s:"(?:" + utilDate.dayNames.join("|") + ")"
3704             };
3705         },
3706         N: {
3707             g:0,
3708             c:null,
3709             s:"[1-7]" // ISO-8601 day number (1 (monday) - 7 (sunday))
3710         },
3711         S: {
3712             g:0,
3713             c:null,
3714             s:"(?:st|nd|rd|th)"
3715         },
3716         w: {
3717             g:0,
3718             c:null,
3719             s:"[0-6]" // javascript day number (0 (sunday) - 6 (saturday))
3720         },
3721         z: {
3722             g:1,
3723             c:"z = parseInt(results[{0}], 10);\n",
3724             s:"(\\d{1,3})" // day of the year (0 - 364 (365 in leap years))
3725         },
3726         W: {
3727             g:0,
3728             c:null,
3729             s:"(?:\\d{2})" // ISO-8601 week number (with leading zero)
3730         },
3731         F: function() {
3732             return {
3733                 g:1,
3734                 c:"m = parseInt(Ext.Date.getMonthNumber(results[{0}]), 10);\n", // get localised month number
3735                 s:"(" + utilDate.monthNames.join("|") + ")"
3736             };
3737         },
3738         M: function() {
3739             for (var a = [], i = 0; i < 12; a.push(utilDate.getShortMonthName(i)), ++i); // get localised short month names
3740             return Ext.applyIf({
3741                 s:"(" + a.join("|") + ")"
3742             }, utilDate.formatCodeToRegex("F"));
3743         },
3744         m: {
3745             g:1,
3746             c:"m = parseInt(results[{0}], 10) - 1;\n",
3747             s:"(\\d{2})" // month number with leading zeros (01 - 12)
3748         },
3749         n: {
3750             g:1,
3751             c:"m = parseInt(results[{0}], 10) - 1;\n",
3752             s:"(\\d{1,2})" // month number without leading zeros (1 - 12)
3753         },
3754         t: {
3755             g:0,
3756             c:null,
3757             s:"(?:\\d{2})" // no. of days in the month (28 - 31)
3758         },
3759         L: {
3760             g:0,
3761             c:null,
3762             s:"(?:1|0)"
3763         },
3764         o: function() {
3765             return utilDate.formatCodeToRegex("Y");
3766         },
3767         Y: {
3768             g:1,
3769             c:"y = parseInt(results[{0}], 10);\n",
3770             s:"(\\d{4})" // 4-digit year
3771         },
3772         y: {
3773             g:1,
3774             c:"var ty = parseInt(results[{0}], 10);\n"
3775                 + "y = ty > Ext.Date.y2kYear ? 1900 + ty : 2000 + ty;\n", // 2-digit year
3776             s:"(\\d{1,2})"
3777         },
3778         /**
3779          * In the am/pm parsing routines, we allow both upper and lower case
3780          * even though it doesn't exactly match the spec. It gives much more flexibility
3781          * in being able to specify case insensitive regexes.
3782          */
3783         a: {
3784             g:1,
3785             c:"if (/(am)/i.test(results[{0}])) {\n"
3786                 + "if (!h || h == 12) { h = 0; }\n"
3787                 + "} else { if (!h || h < 12) { h = (h || 0) + 12; }}",
3788             s:"(am|pm|AM|PM)"
3789         },
3790         A: {
3791             g:1,
3792             c:"if (/(am)/i.test(results[{0}])) {\n"
3793                 + "if (!h || h == 12) { h = 0; }\n"
3794                 + "} else { if (!h || h < 12) { h = (h || 0) + 12; }}",
3795             s:"(AM|PM|am|pm)"
3796         },
3797         g: function() {
3798             return utilDate.formatCodeToRegex("G");
3799         },
3800         G: {
3801             g:1,
3802             c:"h = parseInt(results[{0}], 10);\n",
3803             s:"(\\d{1,2})" // 24-hr format of an hour without leading zeroes (0 - 23)
3804         },
3805         h: function() {
3806             return utilDate.formatCodeToRegex("H");
3807         },
3808         H: {
3809             g:1,
3810             c:"h = parseInt(results[{0}], 10);\n",
3811             s:"(\\d{2})" //  24-hr format of an hour with leading zeroes (00 - 23)
3812         },
3813         i: {
3814             g:1,
3815             c:"i = parseInt(results[{0}], 10);\n",
3816             s:"(\\d{2})" // minutes with leading zeros (00 - 59)
3817         },
3818         s: {
3819             g:1,
3820             c:"s = parseInt(results[{0}], 10);\n",
3821             s:"(\\d{2})" // seconds with leading zeros (00 - 59)
3822         },
3823         u: {
3824             g:1,
3825             c:"ms = results[{0}]; ms = parseInt(ms, 10)/Math.pow(10, ms.length - 3);\n",
3826             s:"(\\d+)" // decimal fraction of a second (minimum = 1 digit, maximum = unlimited)
3827         },
3828         O: {
3829             g:1,
3830             c:[
3831                 "o = results[{0}];",
3832                 "var sn = o.substring(0,1),", // get + / - sign
3833                     "hr = o.substring(1,3)*1 + Math.floor(o.substring(3,5) / 60),", // get hours (performs minutes-to-hour conversion also, just in case)
3834                     "mn = o.substring(3,5) % 60;", // get minutes
3835                 "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
3836             ].join("\n"),
3837             s: "([+\-]\\d{4})" // GMT offset in hrs and mins
3838         },
3839         P: {
3840             g:1,
3841             c:[
3842                 "o = results[{0}];",
3843                 "var sn = o.substring(0,1),", // get + / - sign
3844                     "hr = o.substring(1,3)*1 + Math.floor(o.substring(4,6) / 60),", // get hours (performs minutes-to-hour conversion also, just in case)
3845                     "mn = o.substring(4,6) % 60;", // get minutes
3846                 "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
3847             ].join("\n"),
3848             s: "([+\-]\\d{2}:\\d{2})" // GMT offset in hrs and mins (with colon separator)
3849         },
3850         T: {
3851             g:0,
3852             c:null,
3853             s:"[A-Z]{1,4}" // timezone abbrev. may be between 1 - 4 chars
3854         },
3855         Z: {
3856             g:1,
3857             c:"zz = results[{0}] * 1;\n" // -43200 <= UTC offset <= 50400
3858                   + "zz = (-43200 <= zz && zz <= 50400)? zz : null;\n",
3859             s:"([+\-]?\\d{1,5})" // leading '+' sign is optional for UTC offset
3860         },
3861         c: function() {
3862             var calc = [],
3863                 arr = [
3864                     utilDate.formatCodeToRegex("Y", 1), // year
3865                     utilDate.formatCodeToRegex("m", 2), // month
3866                     utilDate.formatCodeToRegex("d", 3), // day
3867                     utilDate.formatCodeToRegex("h", 4), // hour
3868                     utilDate.formatCodeToRegex("i", 5), // minute
3869                     utilDate.formatCodeToRegex("s", 6), // second
3870                     {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)
3871                     {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
3872                         "if(results[8]) {", // timezone specified
3873                             "if(results[8] == 'Z'){",
3874                                 "zz = 0;", // UTC
3875                             "}else if (results[8].indexOf(':') > -1){",
3876                                 utilDate.formatCodeToRegex("P", 8).c, // timezone offset with colon separator
3877                             "}else{",
3878                                 utilDate.formatCodeToRegex("O", 8).c, // timezone offset without colon separator
3879                             "}",
3880                         "}"
3881                     ].join('\n')}
3882                 ];
3883
3884             for (var i = 0, l = arr.length; i < l; ++i) {
3885                 calc.push(arr[i].c);
3886             }
3887
3888             return {
3889                 g:1,
3890                 c:calc.join(""),
3891                 s:[
3892                     arr[0].s, // year (required)
3893                     "(?:", "-", arr[1].s, // month (optional)
3894                         "(?:", "-", arr[2].s, // day (optional)
3895                             "(?:",
3896                                 "(?:T| )?", // time delimiter -- either a "T" or a single blank space
3897                                 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
3898                                 "(?::", arr[5].s, ")?", // seconds (optional)
3899                                 "(?:(?:\\.|,)(\\d+))?", // decimal fraction of a second (e.g. ",12345" or ".98765") (optional)
3900                                 "(Z|(?:[-+]\\d{2}(?::)?\\d{2}))?", // "Z" (UTC) or "-0530" (UTC offset without colon delimiter) or "+08:00" (UTC offset with colon delimiter) (optional)
3901                             ")?",
3902                         ")?",
3903                     ")?"
3904                 ].join("")
3905             };
3906         },
3907         U: {
3908             g:1,
3909             c:"u = parseInt(results[{0}], 10);\n",
3910             s:"(-?\\d+)" // leading minus sign indicates seconds before UNIX epoch
3911         }
3912     },
3913
3914     //Old Ext.Date prototype methods.
3915     // private
3916     dateFormat: function(date, format) {
3917         return utilDate.format(date, format);
3918     },
3919
3920     /**
3921      * Formats a date given the supplied format string.
3922      * @param {Date} date The date to format
3923      * @param {String} format The format string
3924      * @return {String} The formatted date
3925      */
3926     format: function(date, format) {
3927         if (utilDate.formatFunctions[format] == null) {
3928             utilDate.createFormat(format);
3929         }
3930         var result = utilDate.formatFunctions[format].call(date);
3931         return result + '';
3932     },
3933
3934     /**
3935      * Get the timezone abbreviation of the current date (equivalent to the format specifier 'T').
3936      *
3937      * Note: The date string returned by the javascript Date object's toString() method varies
3938      * between browsers (e.g. FF vs IE) and system region settings (e.g. IE in Asia vs IE in America).
3939      * For a given date string e.g. "Thu Oct 25 2007 22:55:35 GMT+0800 (Malay Peninsula Standard Time)",
3940      * getTimezone() first tries to get the timezone abbreviation from between a pair of parentheses
3941      * (which may or may not be present), failing which it proceeds to get the timezone abbreviation
3942      * from the GMT offset portion of the date string.
3943      * @param {Date} date The date
3944      * @return {String} The abbreviated timezone name (e.g. 'CST', 'PDT', 'EDT', 'MPST' ...).
3945      */
3946     getTimezone : function(date) {
3947         // the following list shows the differences between date strings from different browsers on a WinXP SP2 machine from an Asian locale:
3948         //
3949         // Opera  : "Thu, 25 Oct 2007 22:53:45 GMT+0800" -- shortest (weirdest) date string of the lot
3950         // 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)
3951         // FF     : "Thu Oct 25 2007 22:55:35 GMT+0800 (Malay Peninsula Standard Time)" -- value in parentheses always gives the correct timezone
3952         // IE     : "Thu Oct 25 22:54:35 UTC+0800 2007" -- (Asian system setting) look for 3-4 letter timezone abbrev
3953         // IE     : "Thu Oct 25 17:06:37 PDT 2007" -- (American system setting) look for 3-4 letter timezone abbrev
3954         //
3955         // this crazy regex attempts to guess the correct timezone abbreviation despite these differences.
3956         // step 1: (?:\((.*)\) -- find timezone in parentheses
3957         // 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
3958         // step 3: remove all non uppercase characters found in step 1 and 2
3959         return date.toString().replace(/^.* (?:\((.*)\)|([A-Z]{1,4})(?:[\-+][0-9]{4})?(?: -?\d+)?)$/, "$1$2").replace(/[^A-Z]/g, "");
3960     },
3961
3962     /**
3963      * Get the offset from GMT of the current date (equivalent to the format specifier 'O').
3964      * @param {Date} date The date
3965      * @param {Boolean} colon (optional) true to separate the hours and minutes with a colon (defaults to false).
3966      * @return {String} The 4-character offset string prefixed with + or - (e.g. '-0600').
3967      */
3968     getGMTOffset : function(date, colon) {
3969         var offset = date.getTimezoneOffset();
3970         return (offset > 0 ? "-" : "+")
3971             + Ext.String.leftPad(Math.floor(Math.abs(offset) / 60), 2, "0")
3972             + (colon ? ":" : "")
3973             + Ext.String.leftPad(Math.abs(offset % 60), 2, "0");
3974     },
3975
3976     /**
3977      * Get the numeric day number of the year, adjusted for leap year.
3978      * @param {Date} date The date
3979      * @return {Number} 0 to 364 (365 in leap years).
3980      */
3981     getDayOfYear: function(date) {
3982         var num = 0,
3983             d = Ext.Date.clone(date),
3984             m = date.getMonth(),
3985             i;
3986
3987         for (i = 0, d.setDate(1), d.setMonth(0); i < m; d.setMonth(++i)) {
3988             num += utilDate.getDaysInMonth(d);
3989         }
3990         return num + date.getDate() - 1;
3991     },
3992
3993     /**
3994      * Get the numeric ISO-8601 week number of the year.
3995      * (equivalent to the format specifier 'W', but without a leading zero).
3996      * @param {Date} date The date
3997      * @return {Number} 1 to 53
3998      */
3999     getWeekOfYear : (function() {
4000         // adapted from http://www.merlyn.demon.co.uk/weekcalc.htm
4001         var ms1d = 864e5, // milliseconds in a day
4002             ms7d = 7 * ms1d; // milliseconds in a week
4003
4004         return function(date) { // return a closure so constants get calculated only once
4005             var DC3 = Date.UTC(date.getFullYear(), date.getMonth(), date.getDate() + 3) / ms1d, // an Absolute Day Number
4006                 AWN = Math.floor(DC3 / 7), // an Absolute Week Number
4007                 Wyr = new Date(AWN * ms7d).getUTCFullYear();
4008
4009             return AWN - Math.floor(Date.UTC(Wyr, 0, 7) / ms7d) + 1;
4010         };
4011     })(),
4012
4013     /**
4014      * Checks if the current date falls within a leap year.
4015      * @param {Date} date The date
4016      * @return {Boolean} True if the current date falls within a leap year, false otherwise.
4017      */
4018     isLeapYear : function(date) {
4019         var year = date.getFullYear();
4020         return !!((year & 3) == 0 && (year % 100 || (year % 400 == 0 && year)));
4021     },
4022
4023     /**
4024      * Get the first day of the current month, adjusted for leap year.  The returned value
4025      * is the numeric day index within the week (0-6) which can be used in conjunction with
4026      * the {@link #monthNames} array to retrieve the textual day name.
4027      * Example:
4028      * <pre><code>
4029 var dt = new Date('1/10/2007'),
4030     firstDay = Ext.Date.getFirstDayOfMonth(dt);
4031 console.log(Ext.Date.dayNames[firstDay]); //output: 'Monday'
4032      * </code></pre>
4033      * @param {Date} date The date
4034      * @return {Number} The day number (0-6).
4035      */
4036     getFirstDayOfMonth : function(date) {
4037         var day = (date.getDay() - (date.getDate() - 1)) % 7;
4038         return (day < 0) ? (day + 7) : day;
4039     },
4040
4041     /**
4042      * Get the last day of the current month, adjusted for leap year.  The returned value
4043      * is the numeric day index within the week (0-6) which can be used in conjunction with
4044      * the {@link #monthNames} array to retrieve the textual day name.
4045      * Example:
4046      * <pre><code>
4047 var dt = new Date('1/10/2007'),
4048     lastDay = Ext.Date.getLastDayOfMonth(dt);
4049 console.log(Ext.Date.dayNames[lastDay]); //output: 'Wednesday'
4050      * </code></pre>
4051      * @param {Date} date The date
4052      * @return {Number} The day number (0-6).
4053      */
4054     getLastDayOfMonth : function(date) {
4055         return utilDate.getLastDateOfMonth(date).getDay();
4056     },
4057
4058
4059     /**
4060      * Get the date of the first day of the month in which this date resides.
4061      * @param {Date} date The date
4062      * @return {Date}
4063      */
4064     getFirstDateOfMonth : function(date) {
4065         return new Date(date.getFullYear(), date.getMonth(), 1);
4066     },
4067
4068     /**
4069      * Get the date of the last day of the month in which this date resides.
4070      * @param {Date} date The date
4071      * @return {Date}
4072      */
4073     getLastDateOfMonth : function(date) {
4074         return new Date(date.getFullYear(), date.getMonth(), utilDate.getDaysInMonth(date));
4075     },
4076
4077     /**
4078      * Get the number of days in the current month, adjusted for leap year.
4079      * @param {Date} date The date
4080      * @return {Number} The number of days in the month.
4081      */
4082     getDaysInMonth: (function() {
4083         var daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
4084
4085         return function(date) { // return a closure for efficiency
4086             var m = date.getMonth();
4087
4088             return m == 1 && utilDate.isLeapYear(date) ? 29 : daysInMonth[m];
4089         };
4090     })(),
4091
4092     /**
4093      * Get the English ordinal suffix of the current day (equivalent to the format specifier 'S').
4094      * @param {Date} date The date
4095      * @return {String} 'st, 'nd', 'rd' or 'th'.
4096      */
4097     getSuffix : function(date) {
4098         switch (date.getDate()) {
4099             case 1:
4100             case 21:
4101             case 31:
4102                 return "st";
4103             case 2:
4104             case 22:
4105                 return "nd";
4106             case 3:
4107             case 23:
4108                 return "rd";
4109             default:
4110                 return "th";
4111         }
4112     },
4113
4114     /**
4115      * Creates and returns a new Date instance with the exact same date value as the called instance.
4116      * Dates are copied and passed by reference, so if a copied date variable is modified later, the original
4117      * variable will also be changed.  When the intention is to create a new variable that will not
4118      * modify the original instance, you should create a clone.
4119      *
4120      * Example of correctly cloning a date:
4121      * <pre><code>
4122 //wrong way:
4123 var orig = new Date('10/1/2006');
4124 var copy = orig;
4125 copy.setDate(5);
4126 console.log(orig);  //returns 'Thu Oct 05 2006'!
4127
4128 //correct way:
4129 var orig = new Date('10/1/2006'),
4130     copy = Ext.Date.clone(orig);
4131 copy.setDate(5);
4132 console.log(orig);  //returns 'Thu Oct 01 2006'
4133      * </code></pre>
4134      * @param {Date} date The date
4135      * @return {Date} The new Date instance.
4136      */
4137     clone : function(date) {
4138         return new Date(date.getTime());
4139     },
4140
4141     /**
4142      * Checks if the current date is affected by Daylight Saving Time (DST).
4143      * @param {Date} date The date
4144      * @return {Boolean} True if the current date is affected by DST.
4145      */
4146     isDST : function(date) {
4147         // adapted from http://sencha.com/forum/showthread.php?p=247172#post247172
4148         // courtesy of @geoffrey.mcgill
4149         return new Date(date.getFullYear(), 0, 1).getTimezoneOffset() != date.getTimezoneOffset();
4150     },
4151
4152     /**
4153      * Attempts to clear all time information from this Date by setting the time to midnight of the same day,
4154      * automatically adjusting for Daylight Saving Time (DST) where applicable.
4155      * (note: DST timezone information for the browser's host operating system is assumed to be up-to-date)
4156      * @param {Date} date The date
4157      * @param {Boolean} clone true to create a clone of this date, clear the time and return it (defaults to false).
4158      * @return {Date} this or the clone.
4159      */
4160     clearTime : function(date, clone) {
4161         if (clone) {
4162             return Ext.Date.clearTime(Ext.Date.clone(date));
4163         }
4164
4165         // get current date before clearing time
4166         var d = date.getDate();
4167
4168         // clear time
4169         date.setHours(0);
4170         date.setMinutes(0);
4171         date.setSeconds(0);
4172         date.setMilliseconds(0);
4173
4174         if (date.getDate() != d) { // account for DST (i.e. day of month changed when setting hour = 0)
4175             // note: DST adjustments are assumed to occur in multiples of 1 hour (this is almost always the case)
4176             // refer to http://www.timeanddate.com/time/aboutdst.html for the (rare) exceptions to this rule
4177
4178             // increment hour until cloned date == current date
4179             for (var hr = 1, c = utilDate.add(date, Ext.Date.HOUR, hr); c.getDate() != d; hr++, c = utilDate.add(date, Ext.Date.HOUR, hr));
4180
4181             date.setDate(d);
4182             date.setHours(c.getHours());
4183         }
4184
4185         return date;
4186     },
4187
4188     /**
4189      * Provides a convenient method for performing basic date arithmetic. This method
4190      * does not modify the Date instance being called - it creates and returns
4191      * a new Date instance containing the resulting date value.
4192      *
4193      * Examples:
4194      * <pre><code>
4195 // Basic usage:
4196 var dt = Ext.Date.add(new Date('10/29/2006'), Ext.Date.DAY, 5);
4197 console.log(dt); //returns 'Fri Nov 03 2006 00:00:00'
4198
4199 // Negative values will be subtracted:
4200 var dt2 = Ext.Date.add(new Date('10/1/2006'), Ext.Date.DAY, -5);
4201 console.log(dt2); //returns 'Tue Sep 26 2006 00:00:00'
4202
4203      * </code></pre>
4204      *
4205      * @param {Date} date The date to modify
4206      * @param {String} interval A valid date interval enum value.
4207      * @param {Number} value The amount to add to the current date.
4208      * @return {Date} The new Date instance.
4209      */
4210     add : function(date, interval, value) {
4211         var d = Ext.Date.clone(date),
4212             Date = Ext.Date;
4213         if (!interval || value === 0) return d;
4214
4215         switch(interval.toLowerCase()) {
4216             case Ext.Date.MILLI:
4217                 d.setMilliseconds(d.getMilliseconds() + value);
4218                 break;
4219             case Ext.Date.SECOND:
4220                 d.setSeconds(d.getSeconds() + value);
4221                 break;
4222             case Ext.Date.MINUTE:
4223                 d.setMinutes(d.getMinutes() + value);
4224                 break;
4225             case Ext.Date.HOUR:
4226                 d.setHours(d.getHours() + value);
4227                 break;
4228             case Ext.Date.DAY:
4229                 d.setDate(d.getDate() + value);
4230                 break;
4231             case Ext.Date.MONTH:
4232                 var day = date.getDate();
4233                 if (day > 28) {
4234                     day = Math.min(day, Ext.Date.getLastDateOfMonth(Ext.Date.add(Ext.Date.getFirstDateOfMonth(date), 'mo', value)).getDate());
4235                 }
4236                 d.setDate(day);
4237                 d.setMonth(date.getMonth() + value);
4238                 break;
4239             case Ext.Date.YEAR:
4240                 d.setFullYear(date.getFullYear() + value);
4241                 break;
4242         }
4243         return d;
4244     },
4245
4246     /**
4247      * Checks if a date falls on or between the given start and end dates.
4248      * @param {Date} date The date to check
4249      * @param {Date} start Start date
4250      * @param {Date} end End date
4251      * @return {Boolean} true if this date falls on or between the given start and end dates.
4252      */
4253     between : function(date, start, end) {
4254         var t = date.getTime();
4255         return start.getTime() <= t && t <= end.getTime();
4256     },
4257
4258     //Maintains compatibility with old static and prototype window.Date methods.
4259     compat: function() {
4260         var nativeDate = window.Date,
4261             p, u,
4262             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'],
4263             proto = ['dateFormat', 'format', 'getTimezone', 'getGMTOffset', 'getDayOfYear', 'getWeekOfYear', 'isLeapYear', 'getFirstDayOfMonth', 'getLastDayOfMonth', 'getDaysInMonth', 'getSuffix', 'clone', 'isDST', 'clearTime', 'add', 'between'];
4264
4265         //Append statics
4266         Ext.Array.forEach(statics, function(s) {
4267             nativeDate[s] = utilDate[s];
4268         });
4269
4270         //Append to prototype
4271         Ext.Array.forEach(proto, function(s) {
4272             nativeDate.prototype[s] = function() {
4273                 var args = Array.prototype.slice.call(arguments);
4274                 args.unshift(this);
4275                 return utilDate[s].apply(utilDate, args);
4276             };
4277         });
4278     }
4279 };
4280
4281 var utilDate = Ext.Date;
4282
4283 })();
4284
4285 /**
4286  * @author Jacky Nguyen <jacky@sencha.com>
4287  * @docauthor Jacky Nguyen <jacky@sencha.com>
4288  * @class Ext.Base
4289  *
4290  * The root of all classes created with {@link Ext#define}
4291  * All prototype and static members of this class are inherited by any other class
4292  *
4293  */
4294 (function(flexSetter) {
4295
4296 var Base = Ext.Base = function() {};
4297     Base.prototype = {
4298         $className: 'Ext.Base',
4299
4300         $class: Base,
4301
4302         /**
4303          * Get the reference to the current class from which this object was instantiated. Unlike {@link Ext.Base#statics},
4304          * `this.self` is scope-dependent and it's meant to be used for dynamic inheritance. See {@link Ext.Base#statics}
4305          * for a detailed comparison
4306
4307     Ext.define('My.Cat', {
4308         statics: {
4309             speciesName: 'Cat' // My.Cat.speciesName = 'Cat'
4310         },
4311
4312         constructor: function() {
4313             alert(this.self.speciesName); / dependent on 'this'
4314
4315             return this;
4316         },
4317
4318         clone: function() {
4319             return new this.self();
4320         }
4321     });
4322
4323
4324     Ext.define('My.SnowLeopard', {
4325         extend: 'My.Cat',
4326         statics: {
4327             speciesName: 'Snow Leopard'         // My.SnowLeopard.speciesName = 'Snow Leopard'
4328         }
4329     });
4330
4331     var cat = new My.Cat();                     // alerts 'Cat'
4332     var snowLeopard = new My.SnowLeopard();     // alerts 'Snow Leopard'
4333
4334     var clone = snowLeopard.clone();
4335     alert(Ext.getClassName(clone));             // alerts 'My.SnowLeopard'
4336
4337          * @type Class
4338          * @protected
4339          * @markdown
4340          */
4341         self: Base,
4342
4343         /**
4344          * Default constructor, simply returns `this`
4345          *
4346          * @constructor
4347          * @protected
4348          * @return {Object} this
4349          */
4350         constructor: function() {
4351             return this;
4352         },
4353
4354         /**
4355          * Initialize configuration for this class. a typical example:
4356
4357     Ext.define('My.awesome.Class', {
4358         // The default config
4359         config: {
4360             name: 'Awesome',
4361             isAwesome: true
4362         },
4363
4364         constructor: function(config) {
4365             this.initConfig(config);
4366
4367             return this;
4368         }
4369     });
4370
4371     var awesome = new My.awesome.Class({
4372         name: 'Super Awesome'
4373     });
4374
4375     alert(awesome.getName()); // 'Super Awesome'
4376
4377          * @protected
4378          * @param {Object} config
4379          * @return {Object} mixins The mixin prototypes as key - value pairs
4380          * @markdown
4381          */
4382         initConfig: function(config) {
4383             if (!this.$configInited) {
4384                 this.config = Ext.Object.merge({}, this.config || {}, config || {});
4385
4386                 this.applyConfig(this.config);
4387
4388                 this.$configInited = true;
4389             }
4390
4391             return this;
4392         },
4393
4394         /**
4395          * @private
4396          */
4397         setConfig: function(config) {
4398             this.applyConfig(config || {});
4399
4400             return this;
4401         },
4402
4403         /**
4404          * @private
4405          */
4406         applyConfig: flexSetter(function(name, value) {
4407             var setter = 'set' + Ext.String.capitalize(name);
4408
4409             if (typeof this[setter] === 'function') {
4410                 this[setter].call(this, value);
4411             }
4412
4413             return this;
4414         }),
4415
4416         /**
4417          * Call the parent's overridden method. For example:
4418
4419     Ext.define('My.own.A', {
4420         constructor: function(test) {
4421             alert(test);
4422         }
4423     });
4424
4425     Ext.define('My.own.B', {
4426         extend: 'My.own.A',
4427
4428         constructor: function(test) {
4429             alert(test);
4430
4431             this.callParent([test + 1]);
4432         }
4433     });
4434
4435     Ext.define('My.own.C', {
4436         extend: 'My.own.B',
4437
4438         constructor: function() {
4439             alert("Going to call parent's overriden constructor...");
4440
4441             this.callParent(arguments);
4442         }
4443     });
4444
4445     var a = new My.own.A(1); // alerts '1'
4446     var b = new My.own.B(1); // alerts '1', then alerts '2'
4447     var c = new My.own.C(2); // alerts "Going to call parent's overriden constructor..."
4448                              // alerts '2', then alerts '3'
4449
4450          * @protected
4451          * @param {Array/Arguments} args The arguments, either an array or the `arguments` object
4452          * from the current method, for example: `this.callParent(arguments)`
4453          * @return {Mixed} Returns the result from the superclass' method
4454          * @markdown
4455          */
4456         callParent: function(args) {
4457             var method = this.callParent.caller,
4458                 parentClass, methodName;
4459
4460             if (!method.$owner) {
4461                 if (!method.caller) {
4462                     Ext.Error.raise({
4463                         sourceClass: Ext.getClassName(this),
4464                         sourceMethod: "callParent",
4465                         msg: "Attempting to call a protected method from the public scope, which is not allowed"
4466                     });
4467                 }
4468
4469                 method = method.caller;
4470             }
4471
4472             parentClass = method.$owner.superclass;
4473             methodName = method.$name;
4474
4475             if (!(methodName in parentClass)) {
4476                 Ext.Error.raise({
4477                     sourceClass: Ext.getClassName(this),
4478                     sourceMethod: methodName,
4479                     msg: "this.callParent() was called but there's no such method (" + methodName +
4480                          ") found in the parent class (" + (Ext.getClassName(parentClass) || 'Object') + ")"
4481                  });
4482             }
4483
4484             return parentClass[methodName].apply(this, args || []);
4485         },
4486
4487
4488         /**
4489          * Get the reference to the class from which this object was instantiated. Note that unlike {@link Ext.Base#self},
4490          * `this.statics()` is scope-independent and it always returns the class from which it was called, regardless of what
4491          * `this` points to during run-time
4492
4493     Ext.define('My.Cat', {
4494         statics: {
4495             totalCreated: 0,
4496             speciesName: 'Cat' // My.Cat.speciesName = 'Cat'
4497         },
4498
4499         constructor: function() {
4500             var statics = this.statics();
4501
4502             alert(statics.speciesName);     // always equals to 'Cat' no matter what 'this' refers to
4503                                             // equivalent to: My.Cat.speciesName
4504
4505             alert(this.self.speciesName);   // dependent on 'this'
4506
4507             statics.totalCreated++;
4508
4509             return this;
4510         },
4511
4512         clone: function() {
4513             var cloned = new this.self;                      // dependent on 'this'
4514
4515             cloned.groupName = this.statics().speciesName;   // equivalent to: My.Cat.speciesName
4516
4517             return cloned;
4518         }
4519     });
4520
4521
4522     Ext.define('My.SnowLeopard', {
4523         extend: 'My.Cat',
4524
4525         statics: {
4526             speciesName: 'Snow Leopard'     // My.SnowLeopard.speciesName = 'Snow Leopard'
4527         },
4528
4529         constructor: function() {
4530             this.callParent();
4531         }
4532     });
4533
4534     var cat = new My.Cat();                 // alerts 'Cat', then alerts 'Cat'
4535
4536     var snowLeopard = new My.SnowLeopard(); // alerts 'Cat', then alerts 'Snow Leopard'
4537
4538     var clone = snowLeopard.clone();
4539     alert(Ext.getClassName(clone));         // alerts 'My.SnowLeopard'
4540     alert(clone.groupName);                 // alerts 'Cat'
4541
4542     alert(My.Cat.totalCreated);             // alerts 3
4543
4544          * @protected
4545          * @return {Class}
4546          * @markdown
4547          */
4548         statics: function() {
4549             var method = this.statics.caller,
4550                 self = this.self;
4551
4552             if (!method) {
4553                 return self;
4554             }
4555
4556             return method.$owner;
4557         },
4558
4559         /**
4560          * Call the original method that was previously overridden with {@link Ext.Base#override}
4561
4562     Ext.define('My.Cat', {
4563         constructor: function() {
4564             alert("I'm a cat!");
4565
4566             return this;
4567         }
4568     });
4569
4570     My.Cat.override({
4571         constructor: function() {
4572             alert("I'm going to be a cat!");
4573
4574             var instance = this.callOverridden();
4575
4576             alert("Meeeeoooowwww");
4577
4578             return instance;
4579         }
4580     });
4581
4582     var kitty = new My.Cat(); // alerts "I'm going to be a cat!"
4583                               // alerts "I'm a cat!"
4584                               // alerts "Meeeeoooowwww"
4585
4586          * @param {Array/Arguments} args The arguments, either an array or the `arguments` object
4587          * @return {Mixed} Returns the result after calling the overridden method
4588          * @markdown
4589          */
4590         callOverridden: function(args) {
4591             var method = this.callOverridden.caller;
4592
4593             if (!method.$owner) {
4594                 Ext.Error.raise({
4595                     sourceClass: Ext.getClassName(this),
4596                     sourceMethod: "callOverridden",
4597                     msg: "Attempting to call a protected method from the public scope, which is not allowed"
4598                 });
4599             }
4600
4601             if (!method.$previous) {
4602                 Ext.Error.raise({
4603                     sourceClass: Ext.getClassName(this),
4604                     sourceMethod: "callOverridden",
4605                     msg: "this.callOverridden was called in '" + method.$name +
4606                          "' but this method has never been overridden"
4607                  });
4608             }
4609
4610             return method.$previous.apply(this, args || []);
4611         },
4612
4613         destroy: function() {}
4614     };
4615
4616     // These static properties will be copied to every newly created class with {@link Ext#define}
4617     Ext.apply(Ext.Base, {
4618         /**
4619          * Create a new instance of this Class.
4620 Ext.define('My.cool.Class', {
4621     ...
4622 });
4623
4624 My.cool.Class.create({
4625     someConfig: true
4626 });
4627          * @property create
4628          * @static
4629          * @type Function
4630          * @markdown
4631          */
4632         create: function() {
4633             return Ext.create.apply(Ext, [this].concat(Array.prototype.slice.call(arguments, 0)));
4634         },
4635
4636         /**
4637          * @private
4638          */
4639         own: flexSetter(function(name, value) {
4640             if (typeof value === 'function') {
4641                 this.ownMethod(name, value);
4642             }
4643             else {
4644                 this.prototype[name] = value;
4645             }
4646         }),
4647
4648         /**
4649          * @private
4650          */
4651         ownMethod: function(name, fn) {
4652             var originalFn;
4653
4654             if (fn.$owner !== undefined && fn !== Ext.emptyFn) {
4655                 originalFn = fn;
4656
4657                 fn = function() {
4658                     return originalFn.apply(this, arguments);
4659                 };
4660             }
4661
4662             var className;
4663             className = Ext.getClassName(this);
4664             if (className) {
4665                 fn.displayName = className + '#' + name;
4666             }
4667             fn.$owner = this;
4668             fn.$name = name;
4669
4670             this.prototype[name] = fn;
4671         },
4672
4673         /**
4674          * Add / override static properties of this class.
4675
4676     Ext.define('My.cool.Class', {
4677         ...
4678     });
4679
4680     My.cool.Class.addStatics({
4681         someProperty: 'someValue',      // My.cool.Class.someProperty = 'someValue'
4682         method1: function() { ... },    // My.cool.Class.method1 = function() { ... };
4683         method2: function() { ... }     // My.cool.Class.method2 = function() { ... };
4684     });
4685
4686          * @property addStatics
4687          * @static
4688          * @type Function
4689          * @param {Object} members
4690          * @markdown
4691          */
4692         addStatics: function(members) {
4693             for (var name in members) {
4694                 if (members.hasOwnProperty(name)) {
4695                     this[name] = members[name];
4696                 }
4697             }
4698
4699             return this;
4700         },
4701
4702         /**
4703          * Add methods / properties to the prototype of this class.
4704
4705     Ext.define('My.awesome.Cat', {
4706         constructor: function() {
4707             ...
4708         }
4709     });
4710
4711      My.awesome.Cat.implement({
4712          meow: function() {
4713             alert('Meowww...');
4714          }
4715      });
4716
4717      var kitty = new My.awesome.Cat;
4718      kitty.meow();
4719
4720          * @property implement
4721          * @static
4722          * @type Function
4723          * @param {Object} members
4724          * @markdown
4725          */
4726         implement: function(members) {
4727             var prototype = this.prototype,
4728                 name, i, member, previous;
4729             var className = Ext.getClassName(this);
4730             for (name in members) {
4731                 if (members.hasOwnProperty(name)) {
4732                     member = members[name];
4733
4734                     if (typeof member === 'function') {
4735                         member.$owner = this;
4736                         member.$name = name;
4737                         if (className) {
4738                             member.displayName = className + '#' + name;
4739                         }
4740                     }
4741
4742                     prototype[name] = member;
4743                 }
4744             }
4745
4746             if (Ext.enumerables) {
4747                 var enumerables = Ext.enumerables;
4748
4749                 for (i = enumerables.length; i--;) {
4750                     name = enumerables[i];
4751
4752                     if (members.hasOwnProperty(name)) {
4753                         member = members[name];
4754                         member.$owner = this;
4755                         member.$name = name;
4756                         prototype[name] = member;
4757                     }
4758                 }
4759             }
4760         },
4761
4762         /**
4763          * Borrow another class' members to the prototype of this class.
4764
4765 Ext.define('Bank', {
4766     money: '$$$',
4767     printMoney: function() {
4768         alert('$$$$$$$');
4769     }
4770 });
4771
4772 Ext.define('Thief', {
4773     ...
4774 });
4775
4776 Thief.borrow(Bank, ['money', 'printMoney']);
4777
4778 var steve = new Thief();
4779
4780 alert(steve.money); // alerts '$$$'
4781 steve.printMoney(); // alerts '$$$$$$$'
4782
4783          * @property borrow
4784          * @static
4785          * @type Function
4786          * @param {Ext.Base} fromClass The class to borrow members from
4787          * @param {Array/String} members The names of the members to borrow
4788          * @return {Ext.Base} this
4789          * @markdown
4790          */
4791         borrow: function(fromClass, members) {
4792             var fromPrototype = fromClass.prototype,
4793                 i, ln, member;
4794
4795             members = Ext.Array.from(members);
4796
4797             for (i = 0, ln = members.length; i < ln; i++) {
4798                 member = members[i];
4799
4800                 this.own(member, fromPrototype[member]);
4801             }
4802
4803             return this;
4804         },
4805
4806         /**
4807          * Override prototype members of this class. Overridden methods can be invoked via
4808          * {@link Ext.Base#callOverridden}
4809
4810     Ext.define('My.Cat', {
4811         constructor: function() {
4812             alert("I'm a cat!");
4813
4814             return this;
4815         }
4816     });
4817
4818     My.Cat.override({
4819         constructor: function() {
4820             alert("I'm going to be a cat!");
4821
4822             var instance = this.callOverridden();
4823
4824             alert("Meeeeoooowwww");
4825
4826             return instance;
4827         }
4828     });
4829
4830     var kitty = new My.Cat(); // alerts "I'm going to be a cat!"
4831                               // alerts "I'm a cat!"
4832                               // alerts "Meeeeoooowwww"
4833
4834          * @property override
4835          * @static
4836          * @type Function
4837          * @param {Object} members
4838          * @return {Ext.Base} this
4839          * @markdown
4840          */
4841         override: function(members) {
4842             var prototype = this.prototype,
4843                 name, i, member, previous;
4844
4845             for (name in members) {
4846                 if (members.hasOwnProperty(name)) {
4847                     member = members[name];
4848
4849                     if (typeof member === 'function') {
4850                         if (typeof prototype[name] === 'function') {
4851                             previous = prototype[name];
4852                             member.$previous = previous;
4853                         }
4854
4855                         this.ownMethod(name, member);
4856                     }
4857                     else {
4858                         prototype[name] = member;
4859                     }
4860                 }
4861             }
4862
4863             if (Ext.enumerables) {
4864                 var enumerables = Ext.enumerables;
4865
4866                 for (i = enumerables.length; i--;) {
4867                     name = enumerables[i];
4868
4869                     if (members.hasOwnProperty(name)) {
4870                         if (prototype[name] !== undefined) {
4871                             previous = prototype[name];
4872                             members[name].$previous = previous;
4873                         }
4874
4875                         this.ownMethod(name, members[name]);
4876                     }
4877                 }
4878             }
4879
4880             return this;
4881         },
4882
4883         /**
4884          * Used internally by the mixins pre-processor
4885          * @private
4886          */
4887         mixin: flexSetter(function(name, cls) {
4888             var mixin = cls.prototype,
4889                 my = this.prototype,
4890                 i, fn;
4891
4892             for (i in mixin) {
4893                 if (mixin.hasOwnProperty(i)) {
4894                     if (my[i] === undefined) {
4895                         if (typeof mixin[i] === 'function') {
4896                             fn = mixin[i];
4897
4898                             if (fn.$owner === undefined) {
4899                                 this.ownMethod(i, fn);
4900                             }
4901                             else {
4902                                 my[i] = fn;
4903                             }
4904                         }
4905                         else {
4906                             my[i] = mixin[i];
4907                         }
4908                     }
4909                     else if (i === 'config' && my.config && mixin.config) {
4910                         Ext.Object.merge(my.config, mixin.config);
4911                     }
4912                 }
4913             }
4914
4915             if (my.mixins === undefined) {
4916                 my.mixins = {};
4917             }
4918
4919             my.mixins[name] = mixin;
4920         }),
4921
4922         /**
4923          * Get the current class' name in string format.
4924
4925     Ext.define('My.cool.Class', {
4926         constructor: function() {
4927             alert(this.self.getName()); // alerts 'My.cool.Class'
4928         }
4929     });
4930
4931     My.cool.Class.getName(); // 'My.cool.Class'
4932
4933          * @return {String} className
4934          * @markdown
4935          */
4936         getName: function() {
4937             return Ext.getClassName(this);
4938         },
4939
4940         /**
4941          * Create aliases for existing prototype methods. Example:
4942
4943     Ext.define('My.cool.Class', {
4944         method1: function() { ... },
4945         method2: function() { ... }
4946     });
4947
4948     var test = new My.cool.Class();
4949
4950     My.cool.Class.createAlias({
4951         method3: 'method1',
4952         method4: 'method2'
4953     });
4954
4955     test.method3(); // test.method1()
4956
4957     My.cool.Class.createAlias('method5', 'method3');
4958
4959     test.method5(); // test.method3() -> test.method1()
4960
4961          * @property createAlias
4962          * @static
4963          * @type Function
4964          * @param {String/Object} alias The new method name, or an object to set multiple aliases. See
4965          * {@link Ext.Function#flexSetter flexSetter}
4966          * @param {String/Object} origin The original method name
4967          * @markdown
4968          */
4969         createAlias: flexSetter(function(alias, origin) {
4970             this.prototype[alias] = this.prototype[origin];
4971         })
4972     });
4973
4974 })(Ext.Function.flexSetter);
4975
4976 /**
4977  * @author Jacky Nguyen <jacky@sencha.com>
4978  * @docauthor Jacky Nguyen <jacky@sencha.com>
4979  * @class Ext.Class
4980  * 
4981  * Handles class creation throughout the whole framework. Note that most of the time {@link Ext#define Ext.define} should
4982  * be used instead, since it's a higher level wrapper that aliases to {@link Ext.ClassManager#create}
4983  * to enable namespacing and dynamic dependency resolution.
4984  * 
4985  * # Basic syntax: #
4986  * 
4987  *     Ext.define(className, properties);
4988  * 
4989  * in which `properties` is an object represent a collection of properties that apply to the class. See
4990  * {@link Ext.ClassManager#create} for more detailed instructions.
4991  * 
4992  *     Ext.define('Person', {
4993  *          name: 'Unknown',
4994  * 
4995  *          constructor: function(name) {
4996  *              if (name) {
4997  *                  this.name = name;
4998  *              }
4999  * 
5000  *              return this;
5001  *          },
5002  * 
5003  *          eat: function(foodType) {
5004  *              alert("I'm eating: " + foodType);
5005  * 
5006  *              return this;
5007  *          }
5008  *     });
5009  * 
5010  *     var aaron = new Person("Aaron");
5011  *     aaron.eat("Sandwich"); // alert("I'm eating: Sandwich");
5012  * 
5013  * Ext.Class has a powerful set of extensible {@link Ext.Class#registerPreprocessor pre-processors} which takes care of
5014  * everything related to class creation, including but not limited to inheritance, mixins, configuration, statics, etc.
5015  * 
5016  * # Inheritance: #
5017  * 
5018  *     Ext.define('Developer', {
5019  *          extend: 'Person',
5020  * 
5021  *          constructor: function(name, isGeek) {
5022  *              this.isGeek = isGeek;
5023  * 
5024  *              // Apply a method from the parent class' prototype
5025  *              this.callParent([name]);
5026  * 
5027  *              return this;
5028  * 
5029  *          },
5030  * 
5031  *          code: function(language) {
5032  *              alert("I'm coding in: " + language);
5033  * 
5034  *              this.eat("Bugs");
5035  * 
5036  *              return this;
5037  *          }
5038  *     });
5039  * 
5040  *     var jacky = new Developer("Jacky", true);
5041  *     jacky.code("JavaScript"); // alert("I'm coding in: JavaScript");
5042  *                               // alert("I'm eating: Bugs");
5043  * 
5044  * See {@link Ext.Base#callParent} for more details on calling superclass' methods
5045  * 
5046  * # Mixins: #
5047  * 
5048  *     Ext.define('CanPlayGuitar', {
5049  *          playGuitar: function() {
5050  *             alert("F#...G...D...A");
5051  *          }
5052  *     });
5053  * 
5054  *     Ext.define('CanComposeSongs', {
5055  *          composeSongs: function() { ... }
5056  *     });
5057  * 
5058  *     Ext.define('CanSing', {
5059  *          sing: function() {
5060  *              alert("I'm on the highway to hell...")
5061  *          }
5062  *     });
5063  * 
5064  *     Ext.define('Musician', {
5065  *          extend: 'Person',
5066  * 
5067  *          mixins: {
5068  *              canPlayGuitar: 'CanPlayGuitar',
5069  *              canComposeSongs: 'CanComposeSongs',
5070  *              canSing: 'CanSing'
5071  *          }
5072  *     })
5073  * 
5074  *     Ext.define('CoolPerson', {
5075  *          extend: 'Person',
5076  * 
5077  *          mixins: {
5078  *              canPlayGuitar: 'CanPlayGuitar',
5079  *              canSing: 'CanSing'
5080  *          },
5081  * 
5082  *          sing: function() {
5083  *              alert("Ahem....");
5084  * 
5085  *              this.mixins.canSing.sing.call(this);
5086  * 
5087  *              alert("[Playing guitar at the same time...]");
5088  * 
5089  *              this.playGuitar();
5090  *          }
5091  *     });
5092  * 
5093  *     var me = new CoolPerson("Jacky");
5094  * 
5095  *     me.sing(); // alert("Ahem...");
5096  *                // alert("I'm on the highway to hell...");
5097  *                // alert("[Playing guitar at the same time...]");
5098  *                // alert("F#...G...D...A");
5099  * 
5100  * # Config: #
5101  * 
5102  *     Ext.define('SmartPhone', {
5103  *          config: {
5104  *              hasTouchScreen: false,
5105  *              operatingSystem: 'Other',
5106  *              price: 500
5107  *          },
5108  * 
5109  *          isExpensive: false,
5110  * 
5111  *          constructor: function(config) {
5112  *              this.initConfig(config);
5113  * 
5114  *              return this;
5115  *          },
5116  * 
5117  *          applyPrice: function(price) {
5118  *              this.isExpensive = (price > 500);
5119  * 
5120  *              return price;
5121  *          },
5122  * 
5123  *          applyOperatingSystem: function(operatingSystem) {
5124  *              if (!(/^(iOS|Android|BlackBerry)$/i).test(operatingSystem)) {
5125  *                  return 'Other';
5126  *              }
5127  * 
5128  *              return operatingSystem;
5129  *          }
5130  *     });
5131  * 
5132  *     var iPhone = new SmartPhone({
5133  *          hasTouchScreen: true,
5134  *          operatingSystem: 'iOS'
5135  *     });
5136  * 
5137  *     iPhone.getPrice(); // 500;
5138  *     iPhone.getOperatingSystem(); // 'iOS'
5139  *     iPhone.getHasTouchScreen(); // true;
5140  *     iPhone.hasTouchScreen(); // true
5141  * 
5142  *     iPhone.isExpensive; // false;
5143  *     iPhone.setPrice(600);
5144  *     iPhone.getPrice(); // 600
5145  *     iPhone.isExpensive; // true;
5146  * 
5147  *     iPhone.setOperatingSystem('AlienOS');
5148  *     iPhone.getOperatingSystem(); // 'Other'
5149  * 
5150  * # Statics: #
5151  * 
5152  *     Ext.define('Computer', {
5153  *          statics: {
5154  *              factory: function(brand) {
5155  *                 // 'this' in static methods refer to the class itself
5156  *                  return new this(brand);
5157  *              }
5158  *          },
5159  * 
5160  *          constructor: function() { ... }
5161  *     });
5162  * 
5163  *     var dellComputer = Computer.factory('Dell');
5164  * 
5165  * Also see {@link Ext.Base#statics} and {@link Ext.Base#self} for more details on accessing
5166  * static properties within class methods
5167  *
5168  */
5169 (function() {
5170
5171     var Class,
5172         Base = Ext.Base,
5173         baseStaticProperties = [],
5174         baseStaticProperty;
5175
5176     for (baseStaticProperty in Base) {
5177         if (Base.hasOwnProperty(baseStaticProperty)) {
5178             baseStaticProperties.push(baseStaticProperty);
5179         }
5180     }
5181
5182     /**
5183      * @constructor
5184      * @param {Object} classData An object represent the properties of this class
5185      * @param {Function} createdFn Optional, the callback function to be executed when this class is fully created.
5186      * Note that the creation process can be asynchronous depending on the pre-processors used.
5187      * @return {Ext.Base} The newly created class
5188      */
5189     Ext.Class = Class = function(newClass, classData, onClassCreated) {
5190         if (typeof newClass !== 'function') {
5191             onClassCreated = classData;
5192             classData = newClass;
5193             newClass = function() {
5194                 return this.constructor.apply(this, arguments);
5195             };
5196         }
5197
5198         if (!classData) {
5199             classData = {};
5200         }
5201
5202         var preprocessorStack = classData.preprocessors || Class.getDefaultPreprocessors(),
5203             registeredPreprocessors = Class.getPreprocessors(),
5204             index = 0,
5205             preprocessors = [],
5206             preprocessor, preprocessors, staticPropertyName, process, i, j, ln;
5207
5208         for (i = 0, ln = baseStaticProperties.length; i < ln; i++) {
5209             staticPropertyName = baseStaticProperties[i];
5210             newClass[staticPropertyName] = Base[staticPropertyName];
5211         }
5212
5213         delete classData.preprocessors;
5214
5215         for (j = 0, ln = preprocessorStack.length; j < ln; j++) {
5216             preprocessor = preprocessorStack[j];
5217
5218             if (typeof preprocessor === 'string') {
5219                 preprocessor = registeredPreprocessors[preprocessor];
5220
5221                 if (!preprocessor.always) {
5222                     if (classData.hasOwnProperty(preprocessor.name)) {
5223                         preprocessors.push(preprocessor.fn);
5224                     }
5225                 }
5226                 else {
5227                     preprocessors.push(preprocessor.fn);
5228                 }
5229             }
5230             else {
5231                 preprocessors.push(preprocessor);
5232             }
5233         }
5234
5235         classData.onClassCreated = onClassCreated;
5236
5237         classData.onBeforeClassCreated = function(cls, data) {
5238             onClassCreated = data.onClassCreated;
5239
5240             delete data.onBeforeClassCreated;
5241             delete data.onClassCreated;
5242
5243             cls.implement(data);
5244
5245             if (onClassCreated) {
5246                 onClassCreated.call(cls, cls);
5247             }
5248         };
5249
5250         process = function(cls, data) {
5251             preprocessor = preprocessors[index++];
5252
5253             if (!preprocessor) {
5254                 data.onBeforeClassCreated.apply(this, arguments);
5255                 return;
5256             }
5257
5258             if (preprocessor.call(this, cls, data, process) !== false) {
5259                 process.apply(this, arguments);
5260             }
5261         };
5262
5263         process.call(Class, newClass, classData);
5264
5265         return newClass;
5266     };
5267
5268     Ext.apply(Class, {
5269
5270         /** @private */
5271         preprocessors: {},
5272
5273         /**
5274          * Register a new pre-processor to be used during the class creation process
5275          *
5276          * @member Ext.Class registerPreprocessor
5277          * @param {String} name The pre-processor's name
5278          * @param {Function} fn The callback function to be executed. Typical format:
5279
5280     function(cls, data, fn) {
5281         // Your code here
5282
5283         // Execute this when the processing is finished.
5284         // Asynchronous processing is perfectly ok
5285         if (fn) {
5286             fn.call(this, cls, data);
5287         }
5288     });
5289
5290          * Passed arguments for this function are:
5291          *
5292          * - `{Function} cls`: The created class
5293          * - `{Object} data`: The set of properties passed in {@link Ext.Class} constructor
5294          * - `{Function} fn`: The callback function that <b>must</b> to be executed when this pre-processor finishes,
5295          * regardless of whether the processing is synchronous or aynchronous
5296          *
5297          * @return {Ext.Class} this
5298          * @markdown
5299          */
5300         registerPreprocessor: function(name, fn, always) {
5301             this.preprocessors[name] = {
5302                 name: name,
5303                 always: always ||  false,
5304                 fn: fn
5305             };
5306
5307             return this;
5308         },
5309
5310         /**
5311          * Retrieve a pre-processor callback function by its name, which has been registered before
5312          *
5313          * @param {String} name
5314          * @return {Function} preprocessor
5315          */
5316         getPreprocessor: function(name) {
5317             return this.preprocessors[name];
5318         },
5319
5320         getPreprocessors: function() {
5321             return this.preprocessors;
5322         },
5323
5324         /**
5325          * Retrieve the array stack of default pre-processors
5326          *
5327          * @return {Function} defaultPreprocessors
5328          */
5329         getDefaultPreprocessors: function() {
5330             return this.defaultPreprocessors || [];
5331         },
5332
5333         /**
5334          * Set the default array stack of default pre-processors
5335          *
5336          * @param {Array} preprocessors
5337          * @return {Ext.Class} this
5338          */
5339         setDefaultPreprocessors: function(preprocessors) {
5340             this.defaultPreprocessors = Ext.Array.from(preprocessors);
5341
5342             return this;
5343         },
5344
5345         /**
5346          * Insert this pre-processor at a specific position in the stack, optionally relative to
5347          * any existing pre-processor. For example:
5348
5349     Ext.Class.registerPreprocessor('debug', function(cls, data, fn) {
5350         // Your code here
5351
5352         if (fn) {
5353             fn.call(this, cls, data);
5354         }
5355     }).insertDefaultPreprocessor('debug', 'last');
5356
5357          * @param {String} name The pre-processor name. Note that it needs to be registered with
5358          * {@link Ext#registerPreprocessor registerPreprocessor} before this
5359          * @param {String} offset The insertion position. Four possible values are:
5360          * 'first', 'last', or: 'before', 'after' (relative to the name provided in the third argument)
5361          * @param {String} relativeName
5362          * @return {Ext.Class} this
5363          * @markdown
5364          */
5365         setDefaultPreprocessorPosition: function(name, offset, relativeName) {
5366             var defaultPreprocessors = this.defaultPreprocessors,
5367                 index;
5368
5369             if (typeof offset === 'string') {
5370                 if (offset === 'first') {
5371                     defaultPreprocessors.unshift(name);
5372
5373                     return this;
5374                 }
5375                 else if (offset === 'last') {
5376                     defaultPreprocessors.push(name);
5377
5378                     return this;
5379                 }
5380
5381                 offset = (offset === 'after') ? 1 : -1;
5382             }
5383
5384             index = Ext.Array.indexOf(defaultPreprocessors, relativeName);
5385
5386             if (index !== -1) {
5387                 defaultPreprocessors.splice(Math.max(0, index + offset), 0, name);
5388             }
5389
5390             return this;
5391         }
5392     });
5393
5394     Class.registerPreprocessor('extend', function(cls, data) {
5395         var extend = data.extend,
5396             base = Ext.Base,
5397             basePrototype = base.prototype,
5398             prototype = function() {},
5399             parent, i, k, ln, staticName, parentStatics,
5400             parentPrototype, clsPrototype;
5401
5402         if (extend && extend !== Object) {
5403             parent = extend;
5404         }
5405         else {
5406             parent = base;
5407         }
5408
5409         parentPrototype = parent.prototype;
5410
5411         prototype.prototype = parentPrototype;
5412         clsPrototype = cls.prototype = new prototype();
5413
5414         if (!('$class' in parent)) {
5415             for (i in basePrototype) {
5416                 if (!parentPrototype[i]) {
5417                     parentPrototype[i] = basePrototype[i];
5418                 }
5419             }
5420         }
5421
5422         clsPrototype.self = cls;
5423
5424         cls.superclass = clsPrototype.superclass = parentPrototype;
5425
5426         delete data.extend;
5427
5428         // Statics inheritance
5429         parentStatics = parentPrototype.$inheritableStatics;
5430
5431         if (parentStatics) {
5432             for (k = 0, ln = parentStatics.length; k < ln; k++) {
5433                 staticName = parentStatics[k];
5434
5435                 if (!cls.hasOwnProperty(staticName)) {
5436                     cls[staticName] = parent[staticName];
5437                 }
5438             }
5439         }
5440
5441         // Merge the parent class' config object without referencing it
5442         if (parentPrototype.config) {
5443             clsPrototype.config = Ext.Object.merge({}, parentPrototype.config);
5444         }
5445         else {
5446             clsPrototype.config = {};
5447         }
5448
5449         if (clsPrototype.$onExtended) {
5450             clsPrototype.$onExtended.call(cls, cls, data);
5451         }
5452
5453         if (data.onClassExtended) {
5454             clsPrototype.$onExtended = data.onClassExtended;
5455             delete data.onClassExtended;
5456         }
5457
5458     }, true);
5459
5460     Class.registerPreprocessor('statics', function(cls, data) {
5461         var statics = data.statics,
5462             name;
5463
5464         for (name in statics) {
5465             if (statics.hasOwnProperty(name)) {
5466                 cls[name] = statics[name];
5467             }
5468         }
5469
5470         delete data.statics;
5471     });
5472
5473     Class.registerPreprocessor('inheritableStatics', function(cls, data) {
5474         var statics = data.inheritableStatics,
5475             inheritableStatics,
5476             prototype = cls.prototype,
5477             name;
5478
5479         inheritableStatics = prototype.$inheritableStatics;
5480
5481         if (!inheritableStatics) {
5482             inheritableStatics = prototype.$inheritableStatics = [];
5483         }
5484
5485         for (name in statics) {
5486             if (statics.hasOwnProperty(name)) {
5487                 cls[name] = statics[name];
5488                 inheritableStatics.push(name);
5489             }
5490         }
5491
5492         delete data.inheritableStatics;
5493     });
5494
5495     Class.registerPreprocessor('mixins', function(cls, data) {
5496         cls.mixin(data.mixins);
5497
5498         delete data.mixins;
5499     });
5500
5501     Class.registerPreprocessor('config', function(cls, data) {
5502         var prototype = cls.prototype;
5503
5504         Ext.Object.each(data.config, function(name) {
5505             var cName = name.charAt(0).toUpperCase() + name.substr(1),
5506                 pName = name,
5507                 apply = 'apply' + cName,
5508                 setter = 'set' + cName,
5509                 getter = 'get' + cName;
5510
5511             if (!(apply in prototype) && !data.hasOwnProperty(apply)) {
5512                 data[apply] = function(val) {
5513                     return val;
5514                 };
5515             }
5516
5517             if (!(setter in prototype) && !data.hasOwnProperty(setter)) {
5518                 data[setter] = function(val) {
5519                     var ret = this[apply].call(this, val, this[pName]);
5520
5521                     if (ret !== undefined) {
5522                         this[pName] = ret;
5523                     }
5524
5525                     return this;
5526                 };
5527             }
5528
5529             if (!(getter in prototype) && !data.hasOwnProperty(getter)) {
5530                 data[getter] = function() {
5531                     return this[pName];
5532                 };
5533             }
5534         });
5535
5536         Ext.Object.merge(prototype.config, data.config);
5537         delete data.config;
5538     });
5539
5540     Class.setDefaultPreprocessors(['extend', 'statics', 'inheritableStatics', 'mixins', 'config']);
5541
5542     // Backwards compatible
5543     Ext.extend = function(subclass, superclass, members) {
5544         if (arguments.length === 2 && Ext.isObject(superclass)) {
5545             members = superclass;
5546             superclass = subclass;
5547             subclass = null;
5548         }
5549
5550         var cls;
5551
5552         if (!superclass) {
5553             Ext.Error.raise("Attempting to extend from a class which has not been loaded on the page.");
5554         }
5555
5556         members.extend = superclass;
5557         members.preprocessors = ['extend', 'mixins', 'config', 'statics'];
5558
5559         if (subclass) {
5560             cls = new Class(subclass, members);
5561         }
5562         else {
5563             cls = new Class(members);
5564         }
5565
5566         cls.prototype.override = function(o) {
5567             for (var m in o) {
5568                 if (o.hasOwnProperty(m)) {
5569                     this[m] = o[m];
5570                 }
5571             }
5572         };
5573
5574         return cls;
5575     };
5576
5577 })();
5578
5579 /**
5580  * @author Jacky Nguyen <jacky@sencha.com>
5581  * @docauthor Jacky Nguyen <jacky@sencha.com>
5582  * @class Ext.ClassManager
5583
5584 Ext.ClassManager manages all classes and handles mapping from string class name to
5585 actual class objects throughout the whole framework. It is not generally accessed directly, rather through
5586 these convenient shorthands:
5587
5588 - {@link Ext#define Ext.define}
5589 - {@link Ext#create Ext.create}
5590 - {@link Ext#widget Ext.widget}
5591 - {@link Ext#getClass Ext.getClass}
5592 - {@link Ext#getClassName Ext.getClassName}
5593
5594  * @singleton
5595  * @markdown
5596  */
5597 (function(Class, alias) {
5598
5599     var slice = Array.prototype.slice;
5600
5601     var Manager = Ext.ClassManager = {
5602
5603         /**
5604          * @property classes
5605          * @type Object
5606          * All classes which were defined through the ClassManager. Keys are the
5607          * name of the classes and the values are references to the classes.
5608          * @private
5609          */
5610         classes: {},
5611
5612         /**
5613          * @private
5614          */
5615         existCache: {},
5616
5617         /**
5618          * @private
5619          */
5620         namespaceRewrites: [{
5621             from: 'Ext.',
5622             to: Ext
5623         }],
5624
5625         /**
5626          * @private
5627          */
5628         maps: {
5629             alternateToName: {},
5630             aliasToName: {},
5631             nameToAliases: {}
5632         },
5633
5634         /** @private */
5635         enableNamespaceParseCache: true,
5636
5637         /** @private */
5638         namespaceParseCache: {},
5639
5640         /** @private */
5641         instantiators: [],
5642
5643         /** @private */
5644         instantiationCounts: {},
5645
5646         /**
5647          * Checks if a class has already been created.
5648          *
5649          * @param {String} className
5650          * @return {Boolean} exist
5651          */
5652         isCreated: function(className) {
5653             var i, ln, part, root, parts;
5654
5655             if (typeof className !== 'string' || className.length < 1) {
5656                 Ext.Error.raise({
5657                     sourceClass: "Ext.ClassManager",
5658                     sourceMethod: "exist",
5659                     msg: "Invalid classname, must be a string and must not be empty"
5660                 });
5661             }
5662
5663             if (this.classes.hasOwnProperty(className) || this.existCache.hasOwnProperty(className)) {
5664                 return true;
5665             }
5666
5667             root = Ext.global;
5668             parts = this.parseNamespace(className);
5669
5670             for (i = 0, ln = parts.length; i < ln; i++) {
5671                 part = parts[i];
5672
5673                 if (typeof part !== 'string') {
5674                     root = part;
5675                 } else {
5676                     if (!root || !root[part]) {
5677                         return false;
5678                     }
5679
5680                     root = root[part];
5681                 }
5682             }
5683
5684             Ext.Loader.historyPush(className);
5685
5686             this.existCache[className] = true;
5687
5688             return true;
5689         },
5690
5691         /**
5692          * Supports namespace rewriting
5693          * @private
5694          */
5695         parseNamespace: function(namespace) {
5696             if (typeof namespace !== 'string') {
5697                 Ext.Error.raise({
5698                     sourceClass: "Ext.ClassManager",
5699                     sourceMethod: "parseNamespace",
5700                     msg: "Invalid namespace, must be a string"
5701                 });
5702             }
5703
5704             var cache = this.namespaceParseCache;
5705
5706             if (this.enableNamespaceParseCache) {
5707                 if (cache.hasOwnProperty(namespace)) {
5708                     return cache[namespace];
5709                 }
5710             }
5711
5712             var parts = [],
5713                 rewrites = this.namespaceRewrites,
5714                 rewrite, from, to, i, ln, root = Ext.global;
5715
5716             for (i = 0, ln = rewrites.length; i < ln; i++) {
5717                 rewrite = rewrites[i];
5718                 from = rewrite.from;
5719                 to = rewrite.to;
5720
5721                 if (namespace === from || namespace.substring(0, from.length) === from) {
5722                     namespace = namespace.substring(from.length);
5723
5724                     if (typeof to !== 'string') {
5725                         root = to;
5726                     } else {
5727                         parts = parts.concat(to.split('.'));
5728                     }
5729
5730                     break;
5731                 }
5732             }
5733
5734             parts.push(root);
5735
5736             parts = parts.concat(namespace.split('.'));
5737
5738             if (this.enableNamespaceParseCache) {
5739                 cache[namespace] = parts;
5740             }
5741
5742             return parts;
5743         },
5744
5745         /**
5746          * Creates a namespace and assign the `value` to the created object
5747
5748     Ext.ClassManager.setNamespace('MyCompany.pkg.Example', someObject);
5749
5750     alert(MyCompany.pkg.Example === someObject); // alerts true
5751
5752          * @param {String} name
5753          * @param {Mixed} value
5754          * @markdown
5755          */
5756         setNamespace: function(name, value) {
5757             var root = Ext.global,
5758                 parts = this.parseNamespace(name),
5759                 leaf = parts.pop(),
5760                 i, ln, part;
5761
5762             for (i = 0, ln = parts.length; i < ln; i++) {
5763                 part = parts[i];
5764
5765                 if (typeof part !== 'string') {
5766                     root = part;
5767                 } else {
5768                     if (!root[part]) {
5769                         root[part] = {};
5770                     }
5771
5772                     root = root[part];
5773                 }
5774             }
5775
5776             root[leaf] = value;
5777
5778             return root[leaf];
5779         },
5780
5781         /**
5782          * The new Ext.ns, supports namespace rewriting
5783          * @private
5784          */
5785         createNamespaces: function() {
5786             var root = Ext.global,
5787                 parts, part, i, j, ln, subLn;
5788
5789             for (i = 0, ln = arguments.length; i < ln; i++) {
5790                 parts = this.parseNamespace(arguments[i]);
5791
5792                 for (j = 0, subLn = parts.length; j < subLn; j++) {
5793                     part = parts[j];
5794
5795                     if (typeof part !== 'string') {
5796                         root = part;
5797                     } else {
5798                         if (!root[part]) {
5799                             root[part] = {};
5800                         }
5801
5802                         root = root[part];
5803                     }
5804                 }
5805             }
5806
5807             return root;
5808         },
5809
5810         /**
5811          * Sets a name reference to a class.
5812          *
5813          * @param {String} name
5814          * @param {Object} value
5815          * @return {Ext.ClassManager} this
5816          */
5817         set: function(name, value) {
5818             var targetName = this.getName(value);
5819
5820             this.classes[name] = this.setNamespace(name, value);
5821
5822             if (targetName && targetName !== name) {
5823                 this.maps.alternateToName[name] = targetName;
5824             }
5825
5826             return this;
5827         },
5828
5829         /**
5830          * Retrieve a class by its name.
5831          *
5832          * @param {String} name
5833          * @return {Class} class
5834          */
5835         get: function(name) {
5836             if (this.classes.hasOwnProperty(name)) {
5837                 return this.classes[name];
5838             }
5839
5840             var root = Ext.global,
5841                 parts = this.parseNamespace(name),
5842                 part, i, ln;
5843
5844             for (i = 0, ln = parts.length; i < ln; i++) {
5845                 part = parts[i];
5846
5847                 if (typeof part !== 'string') {
5848                     root = part;
5849                 } else {
5850                     if (!root || !root[part]) {
5851                         return null;
5852                     }
5853
5854                     root = root[part];
5855                 }
5856             }
5857
5858             return root;
5859         },
5860
5861         /**
5862          * Register the alias for a class.
5863          *
5864          * @param {Class/String} cls a reference to a class or a className
5865          * @param {String} alias Alias to use when referring to this class
5866          */
5867         setAlias: function(cls, alias) {
5868             var aliasToNameMap = this.maps.aliasToName,
5869                 nameToAliasesMap = this.maps.nameToAliases,
5870                 className;
5871
5872             if (typeof cls === 'string') {
5873                 className = cls;
5874             } else {
5875                 className = this.getName(cls);
5876             }
5877
5878             if (alias && aliasToNameMap[alias] !== className) {
5879                 if (aliasToNameMap.hasOwnProperty(alias) && Ext.isDefined(Ext.global.console)) {
5880                     Ext.global.console.log("[Ext.ClassManager] Overriding existing alias: '" + alias + "' " +
5881                         "of: '" + aliasToNameMap[alias] + "' with: '" + className + "'. Be sure it's intentional.");
5882                 }
5883
5884                 aliasToNameMap[alias] = className;
5885             }
5886
5887             if (!nameToAliasesMap[className]) {
5888                 nameToAliasesMap[className] = [];
5889             }
5890
5891             if (alias) {
5892                 Ext.Array.include(nameToAliasesMap[className], alias);
5893             }
5894
5895             return this;
5896         },
5897
5898         /**
5899          * Get a reference to the class by its alias.
5900          *
5901          * @param {String} alias
5902          * @return {Class} class
5903          */
5904         getByAlias: function(alias) {
5905             return this.get(this.getNameByAlias(alias));
5906         },
5907
5908         /**
5909          * Get the name of a class by its alias.
5910          *
5911          * @param {String} alias
5912          * @return {String} className
5913          */
5914         getNameByAlias: function(alias) {
5915             return this.maps.aliasToName[alias] || '';
5916         },
5917
5918         /**
5919          * Get the name of a class by its alternate name.
5920          *
5921          * @param {String} alternate
5922          * @return {String} className
5923          */
5924         getNameByAlternate: function(alternate) {
5925             return this.maps.alternateToName[alternate] || '';
5926         },
5927
5928         /**
5929          * Get the aliases of a class by the class name
5930          *
5931          * @param {String} name
5932          * @return {Array} aliases
5933          */
5934         getAliasesByName: function(name) {
5935             return this.maps.nameToAliases[name] || [];
5936         },
5937
5938         /**
5939          * Get the name of the class by its reference or its instance;
5940          * usually invoked by the shorthand {@link Ext#getClassName Ext.getClassName}
5941
5942     Ext.ClassManager.getName(Ext.Action); // returns "Ext.Action"
5943
5944          * @param {Class/Object} object
5945          * @return {String} className
5946          * @markdown
5947          */
5948         getName: function(object) {
5949             return object && object.$className || '';
5950         },
5951
5952         /**
5953          * Get the class of the provided object; returns null if it's not an instance
5954          * of any class created with Ext.define. This is usually invoked by the shorthand {@link Ext#getClass Ext.getClass}
5955          *
5956     var component = new Ext.Component();
5957
5958     Ext.ClassManager.getClass(component); // returns Ext.Component
5959              *
5960          * @param {Object} object
5961          * @return {Class} class
5962          * @markdown
5963          */
5964         getClass: function(object) {
5965             return object && object.self || null;
5966         },
5967
5968         /**
5969          * Defines a class. This is usually invoked via the alias {@link Ext#define Ext.define}
5970
5971     Ext.ClassManager.create('My.awesome.Class', {
5972         someProperty: 'something',
5973         someMethod: function() { ... }
5974         ...
5975
5976     }, function() {
5977         alert('Created!');
5978         alert(this === My.awesome.Class); // alerts true
5979
5980         var myInstance = new this();
5981     });
5982
5983          * @param {String} className The class name to create in string dot-namespaced format, for example:
5984          * 'My.very.awesome.Class', 'FeedViewer.plugin.CoolPager'
5985          * It is highly recommended to follow this simple convention:
5986
5987 - The root and the class name are 'CamelCased'
5988 - Everything else is lower-cased
5989
5990          * @param {Object} data The key - value pairs of properties to apply to this class. Property names can be of any valid
5991          * strings, except those in the reserved listed below:
5992
5993 - `mixins`
5994 - `statics`
5995 - `config`
5996 - `alias`
5997 - `self`
5998 - `singleton`
5999 - `alternateClassName`
6000          *
6001          * @param {Function} createdFn Optional callback to execute after the class is created, the execution scope of which
6002          * (`this`) will be the newly created class itself.
6003          * @return {Ext.Base}
6004          * @markdown
6005          */
6006         create: function(className, data, createdFn) {
6007             var manager = this;
6008
6009             if (typeof className !== 'string') {
6010                 Ext.Error.raise({
6011                     sourceClass: "Ext",
6012                     sourceMethod: "define",
6013                     msg: "Invalid class name '" + className + "' specified, must be a non-empty string"
6014                 });
6015             }
6016
6017             data.$className = className;
6018
6019             return new Class(data, function() {
6020                 var postprocessorStack = data.postprocessors || manager.defaultPostprocessors,
6021                     registeredPostprocessors = manager.postprocessors,
6022                     index = 0,
6023                     postprocessors = [],
6024                     postprocessor, postprocessors, process, i, ln;
6025
6026                 delete data.postprocessors;
6027
6028                 for (i = 0, ln = postprocessorStack.length; i < ln; i++) {
6029                     postprocessor = postprocessorStack[i];
6030
6031                     if (typeof postprocessor === 'string') {
6032                         postprocessor = registeredPostprocessors[postprocessor];
6033
6034                         if (!postprocessor.always) {
6035                             if (data[postprocessor.name] !== undefined) {
6036                                 postprocessors.push(postprocessor.fn);
6037                             }
6038                         }
6039                         else {
6040                             postprocessors.push(postprocessor.fn);
6041                         }
6042                     }
6043                     else {
6044                         postprocessors.push(postprocessor);
6045                     }
6046                 }
6047
6048                 process = function(clsName, cls, clsData) {
6049                     postprocessor = postprocessors[index++];
6050
6051                     if (!postprocessor) {
6052                         manager.set(className, cls);
6053
6054                         Ext.Loader.historyPush(className);
6055
6056                         if (createdFn) {
6057                             createdFn.call(cls, cls);
6058                         }
6059
6060                         return;
6061                     }
6062
6063                     if (postprocessor.call(this, clsName, cls, clsData, process) !== false) {
6064                         process.apply(this, arguments);
6065                     }
6066                 };
6067
6068                 process.call(manager, className, this, data);
6069             });
6070         },
6071
6072         /**
6073          * Instantiate a class by its alias; usually invoked by the convenient shorthand {@link Ext#createByAlias Ext.createByAlias}
6074          * If {@link Ext.Loader} is {@link Ext.Loader#setConfig enabled} and the class has not been defined yet, it will
6075          * attempt to load the class via synchronous loading.
6076
6077     var window = Ext.ClassManager.instantiateByAlias('widget.window', { width: 600, height: 800, ... });
6078
6079          * @param {String} alias
6080          * @param {Mixed} args,... Additional arguments after the alias will be passed to the
6081          * class constructor.
6082          * @return {Object} instance
6083          * @markdown
6084          */
6085         instantiateByAlias: function() {
6086             var alias = arguments[0],
6087                 args = slice.call(arguments),
6088                 className = this.getNameByAlias(alias);
6089
6090             if (!className) {
6091                 className = this.maps.aliasToName[alias];
6092
6093                 if (!className) {
6094                     Ext.Error.raise({
6095                         sourceClass: "Ext",
6096                         sourceMethod: "createByAlias",
6097                         msg: "Cannot create an instance of unrecognized alias: " + alias
6098                     });
6099                 }
6100
6101                 if (Ext.global.console) {
6102                     Ext.global.console.warn("[Ext.Loader] Synchronously loading '" + className + "'; consider adding " +
6103                          "Ext.require('" + alias + "') above Ext.onReady");
6104                 }
6105
6106                 Ext.syncRequire(className);
6107             }
6108
6109             args[0] = className;
6110
6111             return this.instantiate.apply(this, args);
6112         },
6113
6114         /**
6115          * Instantiate a class by either full name, alias or alternate name; usually invoked by the convenient
6116          * shorthand {@link Ext#create Ext.create}
6117          *
6118          * If {@link Ext.Loader} is {@link Ext.Loader#setConfig enabled} and the class has not been defined yet, it will
6119          * attempt to load the class via synchronous loading.
6120          *
6121          * For example, all these three lines return the same result:
6122
6123     // alias
6124     var window = Ext.ClassManager.instantiate('widget.window', { width: 600, height: 800, ... });
6125
6126     // alternate name
6127     var window = Ext.ClassManager.instantiate('Ext.Window', { width: 600, height: 800, ... });
6128
6129     // full class name
6130     var window = Ext.ClassManager.instantiate('Ext.window.Window', { width: 600, height: 800, ... });
6131
6132          * @param {String} name
6133          * @param {Mixed} args,... Additional arguments after the name will be passed to the class' constructor.
6134          * @return {Object} instance
6135          * @markdown
6136          */
6137         instantiate: function() {
6138             var name = arguments[0],
6139                 args = slice.call(arguments, 1),
6140                 alias = name,
6141                 possibleName, cls;
6142
6143             if (typeof name !== 'function') {
6144                 if ((typeof name !== 'string' || name.length < 1)) {
6145                     Ext.Error.raise({
6146                         sourceClass: "Ext",
6147                         sourceMethod: "create",
6148                         msg: "Invalid class name or alias '" + name + "' specified, must be a non-empty string"
6149                     });
6150                 }
6151
6152                 cls = this.get(name);
6153             }
6154             else {
6155                 cls = name;
6156             }
6157
6158             // No record of this class name, it's possibly an alias, so look it up
6159             if (!cls) {
6160                 possibleName = this.getNameByAlias(name);
6161
6162                 if (possibleName) {
6163                     name = possibleName;
6164
6165                     cls = this.get(name);
6166                 }
6167             }
6168
6169             // Still no record of this class name, it's possibly an alternate name, so look it up
6170             if (!cls) {
6171                 possibleName = this.getNameByAlternate(name);
6172
6173                 if (possibleName) {
6174                     name = possibleName;
6175
6176                     cls = this.get(name);
6177                 }
6178             }
6179
6180             // Still not existing at this point, try to load it via synchronous mode as the last resort
6181             if (!cls) {
6182                 if (Ext.global.console) {
6183                     Ext.global.console.warn("[Ext.Loader] Synchronously loading '" + name + "'; consider adding " +
6184                          "Ext.require('" + ((possibleName) ? alias : name) + "') above Ext.onReady");
6185                 }
6186
6187                 Ext.syncRequire(name);
6188
6189                 cls = this.get(name);
6190             }
6191
6192             if (!cls) {
6193                 Ext.Error.raise({
6194                     sourceClass: "Ext",
6195                     sourceMethod: "create",
6196                     msg: "Cannot create an instance of unrecognized class name / alias: " + alias
6197                 });
6198             }
6199
6200             if (typeof cls !== 'function') {
6201                 Ext.Error.raise({
6202                     sourceClass: "Ext",
6203                     sourceMethod: "create",
6204                     msg: "'" + name + "' is a singleton and cannot be instantiated"
6205                 });
6206             }
6207
6208             if (!this.instantiationCounts[name]) {
6209                 this.instantiationCounts[name] = 0;
6210             }
6211
6212             this.instantiationCounts[name]++;
6213
6214             return this.getInstantiator(args.length)(cls, args);
6215         },
6216
6217         /**
6218          * @private
6219          * @param name
6220          * @param args
6221          */
6222         dynInstantiate: function(name, args) {
6223             args = Ext.Array.from(args, true);
6224             args.unshift(name);
6225
6226             return this.instantiate.apply(this, args);
6227         },
6228
6229         /**
6230          * @private
6231          * @param length
6232          */
6233         getInstantiator: function(length) {
6234             if (!this.instantiators[length]) {
6235                 var i = length,
6236                     args = [];
6237
6238                 for (i = 0; i < length; i++) {
6239                     args.push('a['+i+']');
6240                 }
6241
6242                 this.instantiators[length] = new Function('c', 'a', 'return new c('+args.join(',')+')');
6243             }
6244
6245             return this.instantiators[length];
6246         },
6247
6248         /**
6249          * @private
6250          */
6251         postprocessors: {},
6252
6253         /**
6254          * @private
6255          */
6256         defaultPostprocessors: [],
6257
6258         /**
6259          * Register a post-processor function.
6260          *
6261          * @param {String} name
6262          * @param {Function} postprocessor
6263          */
6264         registerPostprocessor: function(name, fn, always) {
6265             this.postprocessors[name] = {
6266                 name: name,
6267                 always: always ||  false,
6268                 fn: fn
6269             };
6270
6271             return this;
6272         },
6273
6274         /**
6275          * Set the default post processors array stack which are applied to every class.
6276          *
6277          * @param {String/Array} The name of a registered post processor or an array of registered names.
6278          * @return {Ext.ClassManager} this
6279          */
6280         setDefaultPostprocessors: function(postprocessors) {
6281             this.defaultPostprocessors = Ext.Array.from(postprocessors);
6282
6283             return this;
6284         },
6285
6286         /**
6287          * Insert this post-processor at a specific position in the stack, optionally relative to
6288          * any existing post-processor
6289          *
6290          * @param {String} name The post-processor name. Note that it needs to be registered with
6291          * {@link Ext.ClassManager#registerPostprocessor} before this
6292          * @param {String} offset The insertion position. Four possible values are:
6293          * 'first', 'last', or: 'before', 'after' (relative to the name provided in the third argument)
6294          * @param {String} relativeName
6295          * @return {Ext.ClassManager} this
6296          */
6297         setDefaultPostprocessorPosition: function(name, offset, relativeName) {
6298             var defaultPostprocessors = this.defaultPostprocessors,
6299                 index;
6300
6301             if (typeof offset === 'string') {
6302                 if (offset === 'first') {
6303                     defaultPostprocessors.unshift(name);
6304
6305                     return this;
6306                 }
6307                 else if (offset === 'last') {
6308                     defaultPostprocessors.push(name);
6309
6310                     return this;
6311                 }
6312
6313                 offset = (offset === 'after') ? 1 : -1;
6314             }
6315
6316             index = Ext.Array.indexOf(defaultPostprocessors, relativeName);
6317
6318             if (index !== -1) {
6319                 defaultPostprocessors.splice(Math.max(0, index + offset), 0, name);
6320             }
6321
6322             return this;
6323         },
6324
6325         /**
6326          * Converts a string expression to an array of matching class names. An expression can either refers to class aliases
6327          * or class names. Expressions support wildcards:
6328
6329      // returns ['Ext.window.Window']
6330     var window = Ext.ClassManager.getNamesByExpression('widget.window');
6331
6332     // returns ['widget.panel', 'widget.window', ...]
6333     var allWidgets = Ext.ClassManager.getNamesByExpression('widget.*');
6334
6335     // returns ['Ext.data.Store', 'Ext.data.ArrayProxy', ...]
6336     var allData = Ext.ClassManager.getNamesByExpression('Ext.data.*');
6337
6338          * @param {String} expression
6339          * @return {Array} classNames
6340          * @markdown
6341          */
6342         getNamesByExpression: function(expression) {
6343             var nameToAliasesMap = this.maps.nameToAliases,
6344                 names = [],
6345                 name, alias, aliases, possibleName, regex, i, ln;
6346
6347             if (typeof expression !== 'string' || expression.length < 1) {
6348                 Ext.Error.raise({
6349                     sourceClass: "Ext.ClassManager",
6350                     sourceMethod: "getNamesByExpression",
6351                     msg: "Expression " + expression + " is invalid, must be a non-empty string"
6352                 });
6353             }
6354
6355             if (expression.indexOf('*') !== -1) {
6356                 expression = expression.replace(/\*/g, '(.*?)');
6357                 regex = new RegExp('^' + expression + '$');
6358
6359                 for (name in nameToAliasesMap) {
6360                     if (nameToAliasesMap.hasOwnProperty(name)) {
6361                         aliases = nameToAliasesMap[name];
6362
6363                         if (name.search(regex) !== -1) {
6364                             names.push(name);
6365                         }
6366                         else {
6367                             for (i = 0, ln = aliases.length; i < ln; i++) {
6368                                 alias = aliases[i];
6369
6370                                 if (alias.search(regex) !== -1) {
6371                                     names.push(name);
6372                                     break;
6373                                 }
6374                             }
6375                         }
6376                     }
6377                 }
6378
6379             } else {
6380                 possibleName = this.getNameByAlias(expression);
6381
6382                 if (possibleName) {
6383                     names.push(possibleName);
6384                 } else {
6385                     possibleName = this.getNameByAlternate(expression);
6386
6387                     if (possibleName) {
6388                         names.push(possibleName);
6389                     } else {
6390                         names.push(expression);
6391                     }
6392                 }
6393             }
6394
6395             return names;
6396         }
6397     };
6398
6399     Manager.registerPostprocessor('alias', function(name, cls, data) {
6400         var aliases = data.alias,
6401             widgetPrefix = 'widget.',
6402             i, ln, alias;
6403
6404         if (!(aliases instanceof Array)) {
6405             aliases = [aliases];
6406         }
6407
6408         for (i = 0, ln = aliases.length; i < ln; i++) {
6409             alias = aliases[i];
6410
6411             if (typeof alias !== 'string') {
6412                 Ext.Error.raise({
6413                     sourceClass: "Ext",
6414                     sourceMethod: "define",
6415                     msg: "Invalid alias of: '" + alias + "' for class: '" + name + "'; must be a valid string"
6416                 });
6417             }
6418
6419             this.setAlias(cls, alias);
6420         }
6421
6422         // This is ugly, will change to make use of parseNamespace for alias later on
6423         for (i = 0, ln = aliases.length; i < ln; i++) {
6424             alias = aliases[i];
6425
6426             if (alias.substring(0, widgetPrefix.length) === widgetPrefix) {
6427                 // Only the first alias with 'widget.' prefix will be used for xtype
6428                 cls.xtype = cls.$xtype = alias.substring(widgetPrefix.length);
6429                 break;
6430             }
6431         }
6432     });
6433
6434     Manager.registerPostprocessor('singleton', function(name, cls, data, fn) {
6435         fn.call(this, name, new cls(), data);
6436         return false;
6437     });
6438
6439     Manager.registerPostprocessor('alternateClassName', function(name, cls, data) {
6440         var alternates = data.alternateClassName,
6441             i, ln, alternate;
6442
6443         if (!(alternates instanceof Array)) {
6444             alternates = [alternates];
6445         }
6446
6447         for (i = 0, ln = alternates.length; i < ln; i++) {
6448             alternate = alternates[i];
6449
6450             if (typeof alternate !== 'string') {
6451                 Ext.Error.raise({
6452                     sourceClass: "Ext",
6453                     sourceMethod: "define",
6454                     msg: "Invalid alternate of: '" + alternate + "' for class: '" + name + "'; must be a valid string"
6455                 });
6456             }
6457
6458             this.set(alternate, cls);
6459         }
6460     });
6461
6462     Manager.setDefaultPostprocessors(['alias', 'singleton', 'alternateClassName']);
6463
6464     Ext.apply(Ext, {
6465         /**
6466          * Convenient shorthand, see {@link Ext.ClassManager#instantiate}
6467          * @member Ext
6468          * @method create
6469          */
6470         create: alias(Manager, 'instantiate'),
6471
6472         /**
6473          * @private
6474          * API to be stablized
6475          *
6476          * @param {Mixed} item
6477          * @param {String} namespace
6478          */
6479         factory: function(item, namespace) {
6480             if (item instanceof Array) {
6481                 var i, ln;
6482
6483                 for (i = 0, ln = item.length; i < ln; i++) {
6484                     item[i] = Ext.factory(item[i], namespace);
6485                 }
6486
6487                 return item;
6488             }
6489
6490             var isString = (typeof item === 'string');
6491
6492             if (isString || (item instanceof Object && item.constructor === Object)) {
6493                 var name, config = {};
6494
6495                 if (isString) {
6496                     name = item;
6497                 }
6498                 else {
6499                     name = item.className;
6500                     config = item;
6501                     delete config.className;
6502                 }
6503
6504                 if (namespace !== undefined && name.indexOf(namespace) === -1) {
6505                     name = namespace + '.' + Ext.String.capitalize(name);
6506                 }
6507
6508                 return Ext.create(name, config);
6509             }
6510
6511             if (typeof item === 'function') {
6512                 return Ext.create(item);
6513             }
6514
6515             return item;
6516         },
6517
6518         /**
6519          * Convenient shorthand to create a widget by its xtype, also see {@link Ext.ClassManager#instantiateByAlias}
6520
6521     var button = Ext.widget('button'); // Equivalent to Ext.create('widget.button')
6522     var panel = Ext.widget('panel'); // Equivalent to Ext.create('widget.panel')
6523
6524          * @member Ext
6525          * @method widget
6526          * @markdown
6527          */
6528         widget: function(name) {
6529             var args = slice.call(arguments);
6530             args[0] = 'widget.' + name;
6531
6532             return Manager.instantiateByAlias.apply(Manager, args);
6533         },
6534
6535         /**
6536          * Convenient shorthand, see {@link Ext.ClassManager#instantiateByAlias}
6537          * @member Ext
6538          * @method createByAlias
6539          */
6540         createByAlias: alias(Manager, 'instantiateByAlias'),
6541
6542         /**
6543          * Convenient shorthand for {@link Ext.ClassManager#create}, see detailed {@link Ext.Class explanation}
6544          * @member Ext
6545          * @method define
6546          */
6547         define: alias(Manager, 'create'),
6548
6549         /**
6550          * Convenient shorthand, see {@link Ext.ClassManager#getName}
6551          * @member Ext
6552          * @method getClassName
6553          */
6554         getClassName: alias(Manager, 'getName'),
6555
6556         /**
6557          *
6558          * @param {Mixed} object
6559          */
6560         getDisplayName: function(object) {
6561             if (object.displayName) {
6562                 return object.displayName;
6563             }
6564
6565             if (object.$name && object.$class) {
6566                 return Ext.getClassName(object.$class) + '#' + object.$name;
6567             }
6568
6569             if (object.$className) {
6570                 return object.$className;
6571             }
6572
6573             return 'Anonymous';
6574         },
6575
6576         /**
6577          * Convenient shorthand, see {@link Ext.ClassManager#getClass}
6578          * @member Ext
6579          * @method getClassName
6580          */
6581         getClass: alias(Manager, 'getClass'),
6582
6583         /**
6584          * Creates namespaces to be used for scoping variables and classes so that they are not global.
6585          * Specifying the last node of a namespace implicitly creates all other nodes. Usage:
6586
6587     Ext.namespace('Company', 'Company.data');
6588
6589      // equivalent and preferable to the above syntax
6590     Ext.namespace('Company.data');
6591
6592     Company.Widget = function() { ... };
6593
6594     Company.data.CustomStore = function(config) { ... };
6595
6596          * @param {String} namespace1
6597          * @param {String} namespace2
6598          * @param {String} etc
6599          * @return {Object} The namespace object. (If multiple arguments are passed, this will be the last namespace created)
6600          * @function
6601          * @member Ext
6602          * @method namespace
6603          * @markdown
6604          */
6605         namespace: alias(Manager, 'createNamespaces')
6606     });
6607
6608     Ext.createWidget = Ext.widget;
6609
6610     /**
6611      * Convenient alias for {@link Ext#namespace Ext.namespace}
6612      * @member Ext
6613      * @method ns
6614      */
6615     Ext.ns = Ext.namespace;
6616
6617     Class.registerPreprocessor('className', function(cls, data) {
6618         if (data.$className) {
6619             cls.$className = data.$className;
6620             cls.displayName = cls.$className;
6621         }
6622     }, true);
6623
6624     Class.setDefaultPreprocessorPosition('className', 'first');
6625
6626 })(Ext.Class, Ext.Function.alias);
6627
6628 /**
6629  * @author Jacky Nguyen <jacky@sencha.com>
6630  * @docauthor Jacky Nguyen <jacky@sencha.com>
6631  * @class Ext.Loader
6632  *
6633
6634 Ext.Loader is the heart of the new dynamic dependency loading capability in Ext JS 4+. It is most commonly used
6635 via the {@link Ext#require} shorthand. Ext.Loader supports both asynchronous and synchronous loading
6636 approaches, and leverage their advantages for the best development flow. We'll discuss about the pros and cons of each approach:
6637
6638 # Asynchronous Loading #
6639
6640 - Advantages:
6641         + Cross-domain
6642         + No web server needed: you can run the application via the file system protocol (i.e: `file://path/to/your/index
6643  .html`)
6644         + Best possible debugging experience: error messages come with the exact file name and line number
6645
6646 - Disadvantages:
6647         + Dependencies need to be specified before-hand
6648
6649 ### Method 1: Explicitly include what you need: ###
6650
6651     // Syntax
6652     Ext.require({String/Array} expressions);
6653
6654     // Example: Single alias
6655     Ext.require('widget.window');
6656
6657     // Example: Single class name
6658     Ext.require('Ext.window.Window');
6659
6660     // Example: Multiple aliases / class names mix
6661     Ext.require(['widget.window', 'layout.border', 'Ext.data.Connection']);
6662
6663     // Wildcards
6664     Ext.require(['widget.*', 'layout.*', 'Ext.data.*']);
6665
6666 ### Method 2: Explicitly exclude what you don't need: ###
6667
6668     // Syntax: Note that it must be in this chaining format.
6669     Ext.exclude({String/Array} expressions)
6670        .require({String/Array} expressions);
6671
6672     // Include everything except Ext.data.*
6673     Ext.exclude('Ext.data.*').require('*'); 
6674
6675     // Include all widgets except widget.checkbox*,
6676     // which will match widget.checkbox, widget.checkboxfield, widget.checkboxgroup, etc.
6677     Ext.exclude('widget.checkbox*').require('widget.*');
6678
6679 # Synchronous Loading on Demand #
6680
6681 - *Advantages:*
6682         + There's no need to specify dependencies before-hand, which is always the convenience of including ext-all.js
6683  before
6684
6685 - *Disadvantages:*
6686         + Not as good debugging experience since file name won't be shown (except in Firebug at the moment)
6687         + Must be from the same domain due to XHR restriction
6688         + Need a web server, same reason as above
6689
6690 There's one simple rule to follow: Instantiate everything with Ext.create instead of the `new` keyword
6691
6692     Ext.create('widget.window', { ... }); // Instead of new Ext.window.Window({...});
6693
6694     Ext.create('Ext.window.Window', {}); // Same as above, using full class name instead of alias
6695
6696     Ext.widget('window', {}); // Same as above, all you need is the traditional `xtype`
6697
6698 Behind the scene, {@link Ext.ClassManager} will automatically check whether the given class name / alias has already
6699  existed on the page. If it's not, Ext.Loader will immediately switch itself to synchronous mode and automatic load the given
6700  class and all its dependencies.
6701
6702 # Hybrid Loading - The Best of Both Worlds #
6703
6704 It has all the advantages combined from asynchronous and synchronous loading. The development flow is simple:
6705
6706 ### Step 1: Start writing your application using synchronous approach. Ext.Loader will automatically fetch all
6707  dependencies on demand as they're needed during run-time. For example: ###
6708
6709     Ext.onReady(function(){
6710         var window = Ext.createWidget('window', {
6711             width: 500,
6712             height: 300,
6713             layout: {
6714                 type: 'border',
6715                 padding: 5
6716             },
6717             title: 'Hello Dialog',
6718             items: [{
6719                 title: 'Navigation',
6720                 collapsible: true,
6721                 region: 'west',
6722                 width: 200,
6723                 html: 'Hello',
6724                 split: true
6725             }, {
6726                 title: 'TabPanel',
6727                 region: 'center'
6728             }]
6729         });
6730
6731         window.show();
6732     })
6733
6734 ### Step 2: Along the way, when you need better debugging ability, watch the console for warnings like these: ###
6735
6736     [Ext.Loader] Synchronously loading 'Ext.window.Window'; consider adding Ext.require('Ext.window.Window') before your application's code
6737     ClassManager.js:432
6738     [Ext.Loader] Synchronously loading 'Ext.layout.container.Border'; consider adding Ext.require('Ext.layout.container.Border') before your application's code
6739
6740 Simply copy and paste the suggested code above `Ext.onReady`, i.e:
6741
6742     Ext.require('Ext.window.Window');
6743     Ext.require('Ext.layout.container.Border');
6744
6745     Ext.onReady(...);
6746
6747 Everything should now load via asynchronous mode.
6748
6749 # Deployment #
6750
6751 It's important to note that dynamic loading should only be used during development on your local machines.
6752 During production, all dependencies should be combined into one single JavaScript file. Ext.Loader makes
6753 the whole process of transitioning from / to between development / maintenance and production as easy as
6754 possible. Internally {@link Ext.Loader#history Ext.Loader.history} maintains the list of all dependencies your application
6755 needs in the exact loading sequence. It's as simple as concatenating all files in this array into one,
6756 then include it on top of your application.
6757
6758 This process will be automated with Sencha Command, to be released and documented towards Ext JS 4 Final.
6759
6760  * @singleton
6761  * @markdown
6762  */
6763
6764 (function(Manager, Class, flexSetter, alias) {
6765
6766     var
6767         dependencyProperties = ['extend', 'mixins', 'requires'],
6768         Loader;
6769
6770     Loader = Ext.Loader = {
6771         /**
6772          * @private
6773          */
6774         documentHead: typeof document !== 'undefined' && (document.head || document.getElementsByTagName('head')[0]),
6775
6776         /**
6777          * Flag indicating whether there are still files being loaded
6778          * @private
6779          */
6780         isLoading: false,
6781
6782         /**
6783          * Maintain the queue for all dependencies. Each item in the array is an object of the format:
6784          * {
6785          *      requires: [...], // The required classes for this queue item
6786          *      callback: function() { ... } // The function to execute when all classes specified in requires exist
6787          * }
6788          * @private
6789          */
6790         queue: [],
6791
6792         /**
6793          * Maintain the list of files that have already been handled so that they never get double-loaded
6794          * @private
6795          */
6796         isFileLoaded: {},
6797
6798         /**
6799          * Maintain the list of listeners to execute when all required scripts are fully loaded
6800          * @private
6801          */
6802         readyListeners: [],
6803
6804         /**
6805          * Contains optional dependencies to be loaded last
6806          * @private
6807          */
6808         optionalRequires: [],
6809
6810         /**
6811          * Map of fully qualified class names to an array of dependent classes.
6812          * @private
6813          */
6814         requiresMap: {},
6815
6816         /**
6817          * @private
6818          */
6819         numPendingFiles: 0,
6820
6821         /**
6822          * @private
6823          */
6824         numLoadedFiles: 0,
6825
6826         /** @private */
6827         hasFileLoadError: false,
6828
6829         /**
6830          * @private
6831          */
6832         classNameToFilePathMap: {},
6833
6834         /**
6835          * An array of class names to keep track of the dependency loading order.
6836          * This is not guaranteed to be the same everytime due to the asynchronous
6837          * nature of the Loader.
6838          *
6839          * @property history
6840          * @type Array
6841          */
6842         history: [],
6843
6844         /**
6845          * Configuration
6846          * @private
6847          */
6848         config: {
6849             /**
6850              * Whether or not to enable the dynamic dependency loading feature
6851              * Defaults to false
6852              * @cfg {Boolean} enabled
6853              */
6854             enabled: false,
6855
6856             /**
6857              * @cfg {Boolean} disableCaching
6858              * Appends current timestamp to script files to prevent caching
6859              * Defaults to true
6860              */
6861             disableCaching: true,
6862
6863             /**
6864              * @cfg {String} disableCachingParam
6865              * The get parameter name for the cache buster's timestamp.
6866              * Defaults to '_dc'
6867              */
6868             disableCachingParam: '_dc',
6869
6870             /**
6871              * @cfg {Object} paths
6872              * The mapping from namespaces to file paths
6873     {
6874         'Ext': '.', // This is set by default, Ext.layout.container.Container will be
6875                     // loaded from ./layout/Container.js
6876
6877         'My': './src/my_own_folder' // My.layout.Container will be loaded from
6878                                     // ./src/my_own_folder/layout/Container.js
6879     }
6880              * Note that all relative paths are relative to the current HTML document.
6881              * If not being specified, for example, <code>Other.awesome.Class</code>
6882              * will simply be loaded from <code>./Other/awesome/Class.js</code>
6883              */
6884             paths: {
6885                 'Ext': '.'
6886             }
6887         },
6888
6889         /**
6890          * Set the configuration for the loader. This should be called right after ext-core.js
6891          * (or ext-core-debug.js) is included in the page, i.e:
6892
6893     <script type="text/javascript" src="ext-core-debug.js"></script>
6894     <script type="text/javascript">
6895       Ext.Loader.setConfig({
6896           enabled: true,
6897           paths: {
6898               'My': 'my_own_path'
6899           }
6900       });
6901     <script>
6902     <script type="text/javascript">
6903       Ext.require(...);
6904
6905       Ext.onReady(function() {
6906           // application code here
6907       });
6908     </script>
6909
6910          * Refer to {@link Ext.Loader#configs} for the list of possible properties
6911          *
6912          * @param {Object} config The config object to override the default values in {@link Ext.Loader#config}
6913          * @return {Ext.Loader} this
6914          * @markdown
6915          */
6916         setConfig: function(name, value) {
6917             if (Ext.isObject(name) && arguments.length === 1) {
6918                 Ext.Object.merge(this.config, name);
6919             }
6920             else {
6921                 this.config[name] = (Ext.isObject(value)) ? Ext.Object.merge(this.config[name], value) : value;
6922             }
6923
6924             return this;
6925         },
6926
6927         /**
6928          * Get the config value corresponding to the specified name. If no name is given, will return the config object
6929          * @param {String} name The config property name
6930          * @return {Object/Mixed}
6931          */
6932         getConfig: function(name) {
6933             if (name) {
6934                 return this.config[name];
6935             }
6936
6937             return this.config;
6938         },
6939
6940         /**
6941          * Sets the path of a namespace.
6942          * For Example:
6943
6944     Ext.Loader.setPath('Ext', '.');
6945
6946          * @param {String/Object} name See {@link Ext.Function#flexSetter flexSetter}
6947          * @param {String} path See {@link Ext.Function#flexSetter flexSetter}
6948          * @return {Ext.Loader} this
6949          * @markdown
6950          */
6951         setPath: flexSetter(function(name, path) {
6952             this.config.paths[name] = path;
6953
6954             return this;
6955         }),
6956
6957         /**
6958          * Translates a className to a file path by adding the
6959          * the proper prefix and converting the .'s to /'s. For example:
6960
6961     Ext.Loader.setPath('My', '/path/to/My');
6962
6963     alert(Ext.Loader.getPath('My.awesome.Class')); // alerts '/path/to/My/awesome/Class.js'
6964
6965          * Note that the deeper namespace levels, if explicitly set, are always resolved first. For example:
6966
6967     Ext.Loader.setPath({
6968         'My': '/path/to/lib',
6969         'My.awesome': '/other/path/for/awesome/stuff',
6970         'My.awesome.more': '/more/awesome/path'
6971     });
6972
6973     alert(Ext.Loader.getPath('My.awesome.Class')); // alerts '/other/path/for/awesome/stuff/Class.js'
6974
6975     alert(Ext.Loader.getPath('My.awesome.more.Class')); // alerts '/more/awesome/path/Class.js'
6976
6977     alert(Ext.Loader.getPath('My.cool.Class')); // alerts '/path/to/lib/cool/Class.js'
6978
6979     alert(Ext.Loader.getPath('Unknown.strange.Stuff')); // alerts 'Unknown/strange/Stuff.js'
6980
6981          * @param {String} className
6982          * @return {String} path
6983          * @markdown
6984          */
6985         getPath: function(className) {
6986             var path = '',
6987                 paths = this.config.paths,
6988                 prefix = this.getPrefix(className);
6989
6990             if (prefix.length > 0) {
6991                 if (prefix === className) {
6992                     return paths[prefix];
6993                 }
6994
6995                 path = paths[prefix];
6996                 className = className.substring(prefix.length + 1);
6997             }
6998
6999             if (path.length > 0) {
7000                 path += '/';
7001             }
7002
7003             return path.replace(/\/\.\//g, '/') + className.replace(/\./g, "/") + '.js';
7004         },
7005
7006         /**
7007          * @private
7008          * @param {String} className
7009          */
7010         getPrefix: function(className) {
7011             var paths = this.config.paths,
7012                 prefix, deepestPrefix = '';
7013
7014             if (paths.hasOwnProperty(className)) {
7015                 return className;
7016             }
7017
7018             for (prefix in paths) {
7019                 if (paths.hasOwnProperty(prefix) && prefix + '.' === className.substring(0, prefix.length + 1)) {
7020                     if (prefix.length > deepestPrefix.length) {
7021                         deepestPrefix = prefix;
7022                     }
7023                 }
7024             }
7025
7026             return deepestPrefix;
7027         },
7028
7029         /**
7030          * Refresh all items in the queue. If all dependencies for an item exist during looping,
7031          * it will execute the callback and call refreshQueue again. Triggers onReady when the queue is
7032          * empty
7033          * @private
7034          */
7035         refreshQueue: function() {
7036             var ln = this.queue.length,
7037                 i, item, j, requires;
7038
7039             if (ln === 0) {
7040                 this.triggerReady();
7041                 return;
7042             }
7043
7044             for (i = 0; i < ln; i++) {
7045                 item = this.queue[i];
7046
7047                 if (item) {
7048                     requires = item.requires;
7049
7050                     // Don't bother checking when the number of files loaded
7051                     // is still less than the array length
7052                     if (requires.length > this.numLoadedFiles) {
7053                         continue;
7054                     }
7055
7056                     j = 0;
7057
7058                     do {
7059                         if (Manager.isCreated(requires[j])) {
7060                             // Take out from the queue
7061                             requires.splice(j, 1);
7062                         }
7063                         else {
7064                             j++;
7065                         }
7066                     } while (j < requires.length);
7067
7068                     if (item.requires.length === 0) {
7069                         this.queue.splice(i, 1);
7070                         item.callback.call(item.scope);
7071                         this.refreshQueue();
7072                         break;
7073                     }
7074                 }
7075             }
7076
7077             return this;
7078         },
7079
7080         /**
7081          * Inject a script element to document's head, call onLoad and onError accordingly
7082          * @private
7083          */
7084         injectScriptElement: function(url, onLoad, onError, scope) {
7085             var script = document.createElement('script'),
7086                 me = this,
7087                 onLoadFn = function() {
7088                     me.cleanupScriptElement(script);
7089                     onLoad.call(scope);
7090                 },
7091                 onErrorFn = function() {
7092                     me.cleanupScriptElement(script);
7093                     onError.call(scope);
7094                 };
7095
7096             script.type = 'text/javascript';
7097             script.src = url;
7098             script.onload = onLoadFn;
7099             script.onerror = onErrorFn;
7100             script.onreadystatechange = function() {
7101                 if (this.readyState === 'loaded' || this.readyState === 'complete') {
7102                     onLoadFn();
7103                 }
7104             };
7105
7106             this.documentHead.appendChild(script);
7107
7108             return script;
7109         },
7110
7111         /**
7112          * @private
7113          */
7114         cleanupScriptElement: function(script) {
7115             script.onload = null;
7116             script.onreadystatechange = null;
7117             script.onerror = null;
7118
7119             return this;
7120         },
7121
7122         /**
7123          * Load a script file, supports both asynchronous and synchronous approaches
7124          *
7125          * @param {String} url
7126          * @param {Function} onLoad
7127          * @param {Scope} scope
7128          * @param {Boolean} synchronous
7129          * @private
7130          */
7131         loadScriptFile: function(url, onLoad, onError, scope, synchronous) {
7132             var me = this,
7133                 noCacheUrl = url + (this.getConfig('disableCaching') ? ('?' + this.getConfig('disableCachingParam') + '=' + Ext.Date.now()) : ''),
7134                 fileName = url.split('/').pop(),
7135                 isCrossOriginRestricted = false,
7136                 xhr, status, onScriptError;
7137
7138             scope = scope || this;
7139
7140             this.isLoading = true;
7141
7142             if (!synchronous) {
7143                 onScriptError = function() {
7144                     onError.call(scope, "Failed loading '" + url + "', please verify that the file exists", synchronous);
7145                 };
7146
7147                 if (!Ext.isReady && Ext.onDocumentReady) {
7148                     Ext.onDocumentReady(function() {
7149                         me.injectScriptElement(noCacheUrl, onLoad, onScriptError, scope);
7150                     });
7151                 }
7152                 else {
7153                     this.injectScriptElement(noCacheUrl, onLoad, onScriptError, scope);
7154                 }
7155             }
7156             else {
7157                 if (typeof XMLHttpRequest !== 'undefined') {
7158                     xhr = new XMLHttpRequest();
7159                 } else {
7160                     xhr = new ActiveXObject('Microsoft.XMLHTTP');
7161                 }
7162
7163                 try {
7164                     xhr.open('GET', noCacheUrl, false);
7165                     xhr.send(null);
7166                 } catch (e) {
7167                     isCrossOriginRestricted = true;
7168                 }
7169
7170                 status = (xhr.status === 1223) ? 204 : xhr.status;
7171
7172                 if (!isCrossOriginRestricted) {
7173                     isCrossOriginRestricted = (status === 0);
7174                 }
7175
7176                 if (isCrossOriginRestricted
7177                 ) {
7178                     onError.call(this, "Failed loading synchronously via XHR: '" + url + "'; It's likely that the file is either " +
7179                                        "being loaded from a different domain or from the local file system whereby cross origin " +
7180                                        "requests are not allowed due to security reasons. Use asynchronous loading with " +
7181                                        "Ext.require instead.", synchronous);
7182                 }
7183                 else if (status >= 200 && status < 300
7184                 ) {
7185                     // Firebug friendly, file names are still shown even though they're eval'ed code
7186                     new Function(xhr.responseText + "\n//@ sourceURL=" + fileName)();
7187
7188                     onLoad.call(scope);
7189                 }
7190                 else {
7191                     onError.call(this, "Failed loading synchronously via XHR: '" + url + "'; please " +
7192                                        "verify that the file exists. " +
7193                                        "XHR status code: " + status, synchronous);
7194                 }
7195
7196                 // Prevent potential IE memory leak
7197                 xhr = null;
7198             }
7199         },
7200
7201         /**
7202          * Explicitly exclude files from being loaded. Useful when used in conjunction with a broad include expression.
7203          * Can be chained with more `require` and `exclude` methods, eg:
7204
7205     Ext.exclude('Ext.data.*').require('*');
7206
7207     Ext.exclude('widget.button*').require('widget.*');
7208
7209          * @param {Array} excludes
7210          * @return {Object} object contains `require` method for chaining
7211          * @markdown
7212          */
7213         exclude: function(excludes) {
7214             var me = this;
7215
7216             return {
7217                 require: function(expressions, fn, scope) {
7218                     return me.require(expressions, fn, scope, excludes);
7219                 },
7220
7221                 syncRequire: function(expressions, fn, scope) {
7222                     return me.syncRequire(expressions, fn, scope, excludes);
7223                 }
7224             };
7225         },
7226
7227         /**
7228          * 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
7229          * @param {String/Array} expressions Can either be a string or an array of string
7230          * @param {Function} fn (Optional) The callback function
7231          * @param {Object} scope (Optional) The execution scope (`this`) of the callback function
7232          * @param {String/Array} excludes (Optional) Classes to be excluded, useful when being used with expressions
7233          * @markdown
7234          */
7235         syncRequire: function() {
7236             this.syncModeEnabled = true;
7237             this.require.apply(this, arguments);
7238             this.refreshQueue();
7239             this.syncModeEnabled = false;
7240         },
7241
7242         /**
7243          * Loads all classes by the given names and all their direct dependencies; optionally executes the given callback function when
7244          * finishes, within the optional scope. This method is aliased by {@link Ext#require Ext.require} for convenience
7245          * @param {String/Array} expressions Can either be a string or an array of string
7246          * @param {Function} fn (Optional) The callback function
7247          * @param {Object} scope (Optional) The execution scope (`this`) of the callback function
7248          * @param {String/Array} excludes (Optional) Classes to be excluded, useful when being used with expressions
7249          * @markdown
7250          */
7251         require: function(expressions, fn, scope, excludes) {
7252             var filePath, expression, exclude, className, excluded = {},
7253                 excludedClassNames = [],
7254                 possibleClassNames = [],
7255                 possibleClassName, classNames = [],
7256                 i, j, ln, subLn;
7257
7258             expressions = Ext.Array.from(expressions);
7259             excludes = Ext.Array.from(excludes);
7260
7261             fn = fn || Ext.emptyFn;
7262
7263             scope = scope || Ext.global;
7264
7265             for (i = 0, ln = excludes.length; i < ln; i++) {
7266                 exclude = excludes[i];
7267
7268                 if (typeof exclude === 'string' && exclude.length > 0) {
7269                     excludedClassNames = Manager.getNamesByExpression(exclude);
7270
7271                     for (j = 0, subLn = excludedClassNames.length; j < subLn; j++) {
7272                         excluded[excludedClassNames[j]] = true;
7273                     }
7274                 }
7275             }
7276
7277             for (i = 0, ln = expressions.length; i < ln; i++) {
7278                 expression = expressions[i];
7279
7280                 if (typeof expression === 'string' && expression.length > 0) {
7281                     possibleClassNames = Manager.getNamesByExpression(expression);
7282
7283                     for (j = 0, subLn = possibleClassNames.length; j < subLn; j++) {
7284                         possibleClassName = possibleClassNames[j];
7285
7286                         if (!excluded.hasOwnProperty(possibleClassName) && !Manager.isCreated(possibleClassName)) {
7287                             Ext.Array.include(classNames, possibleClassName);
7288                         }
7289                     }
7290                 }
7291             }
7292
7293             // If the dynamic dependency feature is not being used, throw an error
7294             // if the dependencies are not defined
7295             if (!this.config.enabled) {
7296                 if (classNames.length > 0) {
7297                     Ext.Error.raise({
7298                         sourceClass: "Ext.Loader",
7299                         sourceMethod: "require",
7300                         msg: "Ext.Loader is not enabled, so dependencies cannot be resolved dynamically. " +
7301                              "Missing required class" + ((classNames.length > 1) ? "es" : "") + ": " + classNames.join(', ')
7302                     });
7303                 }
7304             }
7305
7306             if (classNames.length === 0) {
7307                 fn.call(scope);
7308                 return this;
7309             }
7310
7311             this.queue.push({
7312                 requires: classNames,
7313                 callback: fn,
7314                 scope: scope
7315             });
7316
7317             classNames = classNames.slice();
7318
7319             for (i = 0, ln = classNames.length; i < ln; i++) {
7320                 className = classNames[i];
7321
7322                 if (!this.isFileLoaded.hasOwnProperty(className)) {
7323                     this.isFileLoaded[className] = false;
7324
7325                     filePath = this.getPath(className);
7326
7327                     this.classNameToFilePathMap[className] = filePath;
7328
7329                     this.numPendingFiles++;
7330
7331                     this.loadScriptFile(
7332                         filePath,
7333                         Ext.Function.pass(this.onFileLoaded, [className, filePath], this),
7334                         Ext.Function.pass(this.onFileLoadError, [className, filePath]),
7335                         this,
7336                         this.syncModeEnabled
7337                     );
7338                 }
7339             }
7340
7341             return this;
7342         },
7343
7344         /**
7345          * @private
7346          * @param {String} className
7347          * @param {String} filePath
7348          */
7349         onFileLoaded: function(className, filePath) {
7350             this.numLoadedFiles++;
7351
7352             this.isFileLoaded[className] = true;
7353
7354             this.numPendingFiles--;
7355
7356             if (this.numPendingFiles === 0) {
7357                 this.refreshQueue();
7358             }
7359
7360             if (this.numPendingFiles <= 1) {
7361                 window.status = "Finished loading all dependencies, onReady fired!";
7362             }
7363             else {
7364                 window.status = "Loading dependencies, " + this.numPendingFiles + " files left...";
7365             }
7366
7367             if (!this.syncModeEnabled && this.numPendingFiles === 0 && this.isLoading && !this.hasFileLoadError) {
7368                 var queue = this.queue,
7369                     requires,
7370                     i, ln, j, subLn, missingClasses = [], missingPaths = [];
7371
7372                 for (i = 0, ln = queue.length; i < ln; i++) {
7373                     requires = queue[i].requires;
7374
7375                     for (j = 0, subLn = requires.length; j < ln; j++) {
7376                         if (this.isFileLoaded[requires[j]]) {
7377                             missingClasses.push(requires[j]);
7378                         }
7379                     }
7380                 }
7381
7382                 if (missingClasses.length < 1) {
7383                     return;
7384                 }
7385
7386                 missingClasses = Ext.Array.filter(missingClasses, function(item) {
7387                     return !this.requiresMap.hasOwnProperty(item);
7388                 }, this);
7389
7390                 for (i = 0,ln = missingClasses.length; i < ln; i++) {
7391                     missingPaths.push(this.classNameToFilePathMap[missingClasses[i]]);
7392                 }
7393
7394                 Ext.Error.raise({
7395                     sourceClass: "Ext.Loader",
7396                     sourceMethod: "onFileLoaded",
7397                     msg: "The following classes are not declared even if their files have been " +
7398                             "loaded: '" + missingClasses.join("', '") + "'. Please check the source code of their " +
7399                             "corresponding files for possible typos: '" + missingPaths.join("', '") + "'"
7400                 });
7401             }
7402         },
7403
7404         /**
7405          * @private
7406          */
7407         onFileLoadError: function(className, filePath, errorMessage, isSynchronous) {
7408             this.numPendingFiles--;
7409             this.hasFileLoadError = true;
7410
7411             Ext.Error.raise({
7412                 sourceClass: "Ext.Loader",
7413                 classToLoad: className,
7414                 loadPath: filePath,
7415                 loadingType: isSynchronous ? 'synchronous' : 'async',
7416                 msg: errorMessage
7417             });
7418         },
7419
7420         /**
7421          * @private
7422          */
7423         addOptionalRequires: function(requires) {
7424             var optionalRequires = this.optionalRequires,
7425                 i, ln, require;
7426
7427             requires = Ext.Array.from(requires);
7428
7429             for (i = 0, ln = requires.length; i < ln; i++) {
7430                 require = requires[i];
7431
7432                 Ext.Array.include(optionalRequires, require);
7433             }
7434
7435             return this;
7436         },
7437
7438         /**
7439          * @private
7440          */
7441         triggerReady: function(force) {
7442             var readyListeners = this.readyListeners,
7443                 optionalRequires, listener;
7444
7445             if (this.isLoading || force) {
7446                 this.isLoading = false;
7447
7448                 if (this.optionalRequires.length) {
7449                     // Clone then empty the array to eliminate potential recursive loop issue
7450                     optionalRequires = Ext.Array.clone(this.optionalRequires);
7451
7452                     // Empty the original array
7453                     this.optionalRequires.length = 0;
7454
7455                     this.require(optionalRequires, Ext.Function.pass(this.triggerReady, [true], this), this);
7456                     return this;
7457                 }
7458
7459                 while (readyListeners.length) {
7460                     listener = readyListeners.shift();
7461                     listener.fn.call(listener.scope);
7462
7463                     if (this.isLoading) {
7464                         return this;
7465                     }
7466                 }
7467             }
7468
7469             return this;
7470         },
7471
7472         /**
7473          * Add a new listener to be executed when all required scripts are fully loaded
7474          *
7475          * @param {Function} fn The function callback to be executed
7476          * @param {Object} scope The execution scope (<code>this</code>) of the callback function
7477          * @param {Boolean} withDomReady Whether or not to wait for document dom ready as well
7478          */
7479         onReady: function(fn, scope, withDomReady, options) {
7480             var oldFn;
7481
7482             if (withDomReady !== false && Ext.onDocumentReady) {
7483                 oldFn = fn;
7484
7485                 fn = function() {
7486                     Ext.onDocumentReady(oldFn, scope, options);
7487                 };
7488             }
7489
7490             if (!this.isLoading) {
7491                 fn.call(scope);
7492             }
7493             else {
7494                 this.readyListeners.push({
7495                     fn: fn,
7496                     scope: scope
7497                 });
7498             }
7499         },
7500
7501         /**
7502          * @private
7503          * @param {String} className
7504          */
7505         historyPush: function(className) {
7506             if (className && this.isFileLoaded.hasOwnProperty(className)) {
7507                 Ext.Array.include(this.history, className);
7508             }
7509
7510             return this;
7511         }
7512     };
7513
7514     /**
7515      * Convenient alias of {@link Ext.Loader#require}. Please see the introduction documentation of
7516      * {@link Ext.Loader} for examples.
7517      * @member Ext
7518      * @method require
7519      */
7520     Ext.require = alias(Loader, 'require');
7521
7522     /**
7523      * Synchronous version of {@link Ext#require}, convenient alias of {@link Ext.Loader#syncRequire}.
7524      *
7525      * @member Ext
7526      * @method syncRequire
7527      */
7528     Ext.syncRequire = alias(Loader, 'syncRequire');
7529
7530     /**
7531      * Convenient shortcut to {@link Ext.Loader#exclude}
7532      * @member Ext
7533      * @method exclude
7534      */
7535     Ext.exclude = alias(Loader, 'exclude');
7536
7537     /**
7538      * @member Ext
7539      * @method onReady
7540      */
7541     Ext.onReady = function(fn, scope, options) {
7542         Loader.onReady(fn, scope, true, options);
7543     };
7544
7545     Class.registerPreprocessor('loader', function(cls, data, continueFn) {
7546         var me = this,
7547             dependencies = [],
7548             className = Manager.getName(cls),
7549             i, j, ln, subLn, value, propertyName, propertyValue;
7550
7551         /*
7552         Basically loop through the dependencyProperties, look for string class names and push
7553         them into a stack, regardless of whether the property's value is a string, array or object. For example:
7554         {
7555               extend: 'Ext.MyClass',
7556               requires: ['Ext.some.OtherClass'],
7557               mixins: {
7558                   observable: 'Ext.util.Observable';
7559               }
7560         }
7561         which will later be transformed into:
7562         {
7563               extend: Ext.MyClass,
7564               requires: [Ext.some.OtherClass],
7565               mixins: {
7566                   observable: Ext.util.Observable;
7567               }
7568         }
7569         */
7570
7571         for (i = 0, ln = dependencyProperties.length; i < ln; i++) {
7572             propertyName = dependencyProperties[i];
7573
7574             if (data.hasOwnProperty(propertyName)) {
7575                 propertyValue = data[propertyName];
7576
7577                 if (typeof propertyValue === 'string') {
7578                     dependencies.push(propertyValue);
7579                 }
7580                 else if (propertyValue instanceof Array) {
7581                     for (j = 0, subLn = propertyValue.length; j < subLn; j++) {
7582                         value = propertyValue[j];
7583
7584                         if (typeof value === 'string') {
7585                             dependencies.push(value);
7586                         }
7587                     }
7588                 }
7589                 else {
7590                     for (j in propertyValue) {
7591                         if (propertyValue.hasOwnProperty(j)) {
7592                             value = propertyValue[j];
7593
7594                             if (typeof value === 'string') {
7595                                 dependencies.push(value);
7596                             }
7597                         }
7598                     }
7599                 }
7600             }
7601         }
7602
7603         if (dependencies.length === 0) {
7604 //            Loader.historyPush(className);
7605             return;
7606         }
7607
7608         var deadlockPath = [],
7609             requiresMap = Loader.requiresMap,
7610             detectDeadlock;
7611
7612         /*
7613         Automatically detect deadlocks before-hand,
7614         will throw an error with detailed path for ease of debugging. Examples of deadlock cases:
7615
7616         - A extends B, then B extends A
7617         - A requires B, B requires C, then C requires A
7618
7619         The detectDeadlock function will recursively transverse till the leaf, hence it can detect deadlocks
7620         no matter how deep the path is.
7621         */
7622
7623         if (className) {
7624             requiresMap[className] = dependencies;
7625
7626             detectDeadlock = function(cls) {
7627                 deadlockPath.push(cls);
7628
7629                 if (requiresMap[cls]) {
7630                     if (Ext.Array.contains(requiresMap[cls], className)) {
7631                         Ext.Error.raise({
7632                             sourceClass: "Ext.Loader",
7633                             msg: "Deadlock detected while loading dependencies! '" + className + "' and '" +
7634                                 deadlockPath[1] + "' " + "mutually require each other. Path: " +
7635                                 deadlockPath.join(' -> ') + " -> " + deadlockPath[0]
7636                         });
7637                     }
7638
7639                     for (i = 0, ln = requiresMap[cls].length; i < ln; i++) {
7640                         detectDeadlock(requiresMap[cls][i]);
7641                     }
7642                 }
7643             };
7644
7645             detectDeadlock(className);
7646         }
7647
7648
7649         Loader.require(dependencies, function() {
7650             for (i = 0, ln = dependencyProperties.length; i < ln; i++) {
7651                 propertyName = dependencyProperties[i];
7652
7653                 if (data.hasOwnProperty(propertyName)) {
7654                     propertyValue = data[propertyName];
7655
7656                     if (typeof propertyValue === 'string') {
7657                         data[propertyName] = Manager.get(propertyValue);
7658                     }
7659                     else if (propertyValue instanceof Array) {
7660                         for (j = 0, subLn = propertyValue.length; j < subLn; j++) {
7661                             value = propertyValue[j];
7662
7663                             if (typeof value === 'string') {
7664                                 data[propertyName][j] = Manager.get(value);
7665                             }
7666                         }
7667                     }
7668                     else {
7669                         for (var k in propertyValue) {
7670                             if (propertyValue.hasOwnProperty(k)) {
7671                                 value = propertyValue[k];
7672
7673                                 if (typeof value === 'string') {
7674                                     data[propertyName][k] = Manager.get(value);
7675                                 }
7676                             }
7677                         }
7678                     }
7679                 }
7680             }
7681
7682             continueFn.call(me, cls, data);
7683         });
7684
7685         return false;
7686     }, true);
7687
7688     Class.setDefaultPreprocessorPosition('loader', 'after', 'className');
7689
7690     Manager.registerPostprocessor('uses', function(name, cls, data) {
7691         var uses = Ext.Array.from(data.uses),
7692             items = [],
7693             i, ln, item;
7694
7695         for (i = 0, ln = uses.length; i < ln; i++) {
7696             item = uses[i];
7697
7698             if (typeof item === 'string') {
7699                 items.push(item);
7700             }
7701         }
7702
7703         Loader.addOptionalRequires(items);
7704     });
7705
7706     Manager.setDefaultPostprocessorPosition('uses', 'last');
7707
7708 })(Ext.ClassManager, Ext.Class, Ext.Function.flexSetter, Ext.Function.alias);
7709
7710 /**
7711  * @class Ext.Error
7712  * @private
7713  * @extends Error
7714
7715 A wrapper class for the native JavaScript Error object that adds a few useful capabilities for handling
7716 errors in an Ext application. When you use Ext.Error to {@link #raise} an error from within any class that
7717 uses the Ext 4 class system, the Error class can automatically add the source class and method from which
7718 the error was raised. It also includes logic to automatically log the eroor to the console, if available, 
7719 with additional metadata about the error. In all cases, the error will always be thrown at the end so that
7720 execution will halt.
7721
7722 Ext.Error also offers a global error {@link #handle handling} method that can be overridden in order to 
7723 handle application-wide errors in a single spot. You can optionally {@link #ignore} errors altogether,
7724 although in a real application it's usually a better idea to override the handling function and perform
7725 logging or some other method of reporting the errors in a way that is meaningful to the application.
7726
7727 At its simplest you can simply raise an error as a simple string from within any code:
7728
7729 #Example usage:#
7730
7731     Ext.Error.raise('Something bad happened!');
7732     
7733 If raised from plain JavaScript code, the error will be logged to the console (if available) and the message
7734 displayed. In most cases however you'll be raising errors from within a class, and it may often be useful to add
7735 additional metadata about the error being raised.  The {@link #raise} method can also take a config object.
7736 In this form the `msg` attribute becomes the error description, and any other data added to the config gets
7737 added to the error object and, if the console is available, logged to the console for inspection.
7738
7739 #Example usage:#
7740  
7741     Ext.define('Ext.Foo', {
7742         doSomething: function(option){
7743             if (someCondition === false) {
7744                 Ext.Error.raise({
7745                     msg: 'You cannot do that!',
7746                     option: option,   // whatever was passed into the method
7747                     'error code': 100 // other arbitrary info
7748                 });
7749             }
7750         }
7751     });
7752
7753 If a console is available (that supports the `console.dir` function) you'll see console output like:
7754
7755     An error was raised with the following data:
7756     option:         Object { foo: "bar"}
7757         foo:        "bar"
7758     error code:     100
7759     msg:            "You cannot do that!"
7760     sourceClass:   "Ext.Foo"
7761     sourceMethod:  "doSomething"
7762     
7763     uncaught exception: You cannot do that!
7764
7765 As you can see, the error will report exactly where it was raised and will include as much information as the 
7766 raising code can usefully provide.
7767
7768 If you want to handle all application errors globally you can simply override the static {@link handle} method
7769 and provide whatever handling logic you need. If the method returns true then the error is considered handled
7770 and will not be thrown to the browser. If anything but true is returned then the error will be thrown normally.
7771
7772 #Example usage:#
7773
7774     Ext.Error.handle = function(err) {
7775         if (err.someProperty == 'NotReallyAnError') {
7776             // maybe log something to the application here if applicable
7777             return true;
7778         }
7779         // any non-true return value (including none) will cause the error to be thrown
7780     }
7781
7782  * Create a new Error object
7783  * @param {Object} config The config object
7784  * @markdown
7785  * @author Brian Moeskau <brian@sencha.com>
7786  * @docauthor Brian Moeskau <brian@sencha.com>
7787  */
7788 Ext.Error = Ext.extend(Error, {
7789     statics: {
7790         /**
7791          * @property ignore
7792 Static flag that can be used to globally disable error reporting to the browser if set to true
7793 (defaults to false). Note that if you ignore Ext errors it's likely that some other code may fail
7794 and throw a native JavaScript error thereafter, so use with caution. In most cases it will probably
7795 be preferable to supply a custom error {@link #handle handling} function instead.
7796
7797 #Example usage:#
7798
7799     Ext.Error.ignore = true;
7800
7801          * @markdown
7802          * @static
7803          */
7804         ignore: false,
7805
7806         /**
7807 Raise an error that can include additional data and supports automatic console logging if available. 
7808 You can pass a string error message or an object with the `msg` attribute which will be used as the 
7809 error message. The object can contain any other name-value attributes (or objects) to be logged 
7810 along with the error.
7811
7812 Note that after displaying the error message a JavaScript error will ultimately be thrown so that 
7813 execution will halt.
7814
7815 #Example usage:#
7816
7817     Ext.Error.raise('A simple string error message');
7818
7819     // or...
7820
7821     Ext.define('Ext.Foo', {
7822         doSomething: function(option){
7823             if (someCondition === false) {
7824                 Ext.Error.raise({
7825                     msg: 'You cannot do that!',
7826                     option: option,   // whatever was passed into the method
7827                     'error code': 100 // other arbitrary info
7828                 });
7829             }
7830         }
7831     });
7832          * @param {String/Object} err The error message string, or an object containing the 
7833          * attribute "msg" that will be used as the error message. Any other data included in
7834          * the object will also be logged to the browser console, if available.
7835          * @static
7836          * @markdown
7837          */
7838         raise: function(err){
7839             err = err || {};
7840             if (Ext.isString(err)) {
7841                 err = { msg: err };
7842             }
7843
7844             var method = this.raise.caller;
7845
7846             if (method) {
7847                 if (method.$name) {
7848                     err.sourceMethod = method.$name;
7849                 }
7850                 if (method.$owner) {
7851                     err.sourceClass = method.$owner.$className;
7852                 }
7853             }
7854
7855             if (Ext.Error.handle(err) !== true) {
7856                 var global = Ext.global,
7857                     con = global.console,
7858                     msg = Ext.Error.prototype.toString.call(err),
7859                     noConsoleMsg = 'An uncaught error was raised: "' + msg + 
7860                         '". Use Firebug or Webkit console for additional details.';
7861
7862                 if (con) {
7863                     if (con.dir) {
7864                         con.warn('An uncaught error was raised with the following data:');
7865                         con.dir(err);
7866                     }
7867                     else {
7868                         con.warn(noConsoleMsg);
7869                     }
7870                     if (con.error) {
7871                         con.error(msg);
7872                     }
7873                 }
7874                 else if (global.alert){
7875                     global.alert(noConsoleMsg);
7876                 }
7877                 
7878                 throw new Ext.Error(err);
7879             }
7880         },
7881
7882         /**
7883 Globally handle any Ext errors that may be raised, optionally providing custom logic to
7884 handle different errors individually. Return true from the function to bypass throwing the
7885 error to the browser, otherwise the error will be thrown and execution will halt.
7886
7887 #Example usage:#
7888
7889     Ext.Error.handle = function(err) {
7890         if (err.someProperty == 'NotReallyAnError') {
7891             // maybe log something to the application here if applicable
7892             return true;
7893         }
7894         // any non-true return value (including none) will cause the error to be thrown
7895     }
7896
7897          * @param {Ext.Error} err The Ext.Error object being raised. It will contain any attributes
7898          * that were originally raised with it, plus properties about the method and class from which
7899          * the error originated (if raised from a class that uses the Ext 4 class system).
7900          * @static
7901          * @markdown
7902          */
7903         handle: function(){
7904             return Ext.Error.ignore;
7905         }
7906     },
7907
7908     /**
7909      * @constructor
7910      * @param {String/Object} config The error message string, or an object containing the 
7911      * attribute "msg" that will be used as the error message. Any other data included in
7912      * the object will be applied to the error instance and logged to the browser console, if available.
7913      */
7914     constructor: function(config){
7915         if (Ext.isString(config)) {
7916             config = { msg: config };
7917         }
7918         Ext.apply(this, config);
7919     },
7920
7921     /**
7922 Provides a custom string representation of the error object. This is an override of the base JavaScript 
7923 `Object.toString` method, which is useful so that when logged to the browser console, an error object will 
7924 be displayed with a useful message instead of `[object Object]`, the default `toString` result.
7925
7926 The default implementation will include the error message along with the raising class and method, if available,
7927 but this can be overridden with a custom implementation either at the prototype level (for all errors) or on
7928 a particular error instance, if you want to provide a custom description that will show up in the console.
7929      * @markdown
7930      * @return {String} The error message. If raised from within the Ext 4 class system, the error message
7931      * will also include the raising class and method names, if available.
7932      */
7933     toString: function(){
7934         var me = this,
7935             className = me.className ? me.className  : '',
7936             methodName = me.methodName ? '.' + me.methodName + '(): ' : '',
7937             msg = me.msg || '(No description provided)';
7938
7939         return className + methodName + msg;
7940     }
7941 });
7942
7943
7944 /*
7945 Ext JS - JavaScript Library
7946 Copyright (c) 2006-2011, Sencha Inc.
7947 All rights reserved.
7948 licensing@sencha.com
7949 */
7950 /**
7951  * @class Ext.JSON
7952  * Modified version of Douglas Crockford"s json.js that doesn"t
7953  * mess with the Object prototype
7954  * http://www.json.org/js.html
7955  * @singleton
7956  */
7957 Ext.JSON = new(function() {
7958     var useHasOwn = !! {}.hasOwnProperty,
7959     isNative = function() {
7960         var useNative = null;
7961
7962         return function() {
7963             if (useNative === null) {
7964                 useNative = Ext.USE_NATIVE_JSON && window.JSON && JSON.toString() == '[object JSON]';
7965             }
7966
7967             return useNative;
7968         };
7969     }(),
7970     pad = function(n) {
7971         return n < 10 ? "0" + n : n;
7972     },
7973     doDecode = function(json) {
7974         return eval("(" + json + ')');
7975     },
7976     doEncode = function(o) {
7977         if (!Ext.isDefined(o) || o === null) {
7978             return "null";
7979         } else if (Ext.isArray(o)) {
7980             return encodeArray(o);
7981         } else if (Ext.isDate(o)) {
7982             return Ext.JSON.encodeDate(o);
7983         } else if (Ext.isString(o)) {
7984             return encodeString(o);
7985         } else if (typeof o == "number") {
7986             //don't use isNumber here, since finite checks happen inside isNumber
7987             return isFinite(o) ? String(o) : "null";
7988         } else if (Ext.isBoolean(o)) {
7989             return String(o);
7990         } else if (Ext.isObject(o)) {
7991             return encodeObject(o);
7992         } else if (typeof o === "function") {
7993             return "null";
7994         }
7995         return 'undefined';
7996     },
7997     m = {
7998         "\b": '\\b',
7999         "\t": '\\t',
8000         "\n": '\\n',
8001         "\f": '\\f',
8002         "\r": '\\r',
8003         '"': '\\"',
8004         "\\": '\\\\',
8005         '\x0b': '\\u000b' //ie doesn't handle \v
8006     },
8007     charToReplace = /[\\\"\x00-\x1f\x7f-\uffff]/g,
8008     encodeString = function(s) {
8009         return '"' + s.replace(charToReplace, function(a) {
8010             var c = m[a];
8011             return typeof c === 'string' ? c : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
8012         }) + '"';
8013     },
8014     encodeArray = function(o) {
8015         var a = ["[", ""],
8016         // Note empty string in case there are no serializable members.
8017         len = o.length,
8018         i;
8019         for (i = 0; i < len; i += 1) {
8020             a.push(doEncode(o[i]), ',');
8021         }
8022         // Overwrite trailing comma (or empty string)
8023         a[a.length - 1] = ']';
8024         return a.join("");
8025     },
8026     encodeObject = function(o) {
8027         var a = ["{", ""],
8028         // Note empty string in case there are no serializable members.
8029         i;
8030         for (i in o) {
8031             if (!useHasOwn || o.hasOwnProperty(i)) {
8032                 a.push(doEncode(i), ":", doEncode(o[i]), ',');
8033             }
8034         }
8035         // Overwrite trailing comma (or empty string)
8036         a[a.length - 1] = '}';
8037         return a.join("");
8038     };
8039
8040     /**
8041      * <p>Encodes a Date. This returns the actual string which is inserted into the JSON string as the literal expression.
8042      * <b>The returned value includes enclosing double quotation marks.</b></p>
8043      * <p>The default return format is "yyyy-mm-ddThh:mm:ss".</p>
8044      * <p>To override this:</p><pre><code>
8045      Ext.JSON.encodeDate = function(d) {
8046      return d.format('"Y-m-d"');
8047      };
8048      </code></pre>
8049      * @param {Date} d The Date to encode
8050      * @return {String} The string literal to use in a JSON string.
8051      */
8052     this.encodeDate = function(o) {
8053         return '"' + o.getFullYear() + "-" 
8054         + pad(o.getMonth() + 1) + "-"
8055         + pad(o.getDate()) + "T"
8056         + pad(o.getHours()) + ":"
8057         + pad(o.getMinutes()) + ":"
8058         + pad(o.getSeconds()) + '"';
8059     };
8060
8061     /**
8062      * Encodes an Object, Array or other value
8063      * @param {Mixed} o The variable to encode
8064      * @return {String} The JSON string
8065      */
8066     this.encode = function() {
8067         var ec;
8068         return function(o) {
8069             if (!ec) {
8070                 // setup encoding function on first access
8071                 ec = isNative() ? JSON.stringify : doEncode;
8072             }
8073             return ec(o);
8074         };
8075     }();
8076
8077
8078     /**
8079      * Decodes (parses) a JSON string to an object. If the JSON is invalid, this function throws a SyntaxError unless the safe option is set.
8080      * @param {String} json The JSON string
8081      * @param {Boolean} safe (optional) Whether to return null or throw an exception if the JSON is invalid.
8082      * @return {Object} The resulting object
8083      */
8084     this.decode = function() {
8085         var dc;
8086         return function(json, safe) {
8087             if (!dc) {
8088                 // setup decoding function on first access
8089                 dc = isNative() ? JSON.parse : doDecode;
8090             }
8091             try {
8092                 return dc(json);
8093             } catch (e) {
8094                 if (safe === true) {
8095                     return null;
8096                 }
8097                 Ext.Error.raise({
8098                     sourceClass: "Ext.JSON",
8099                     sourceMethod: "decode",
8100                     msg: "You're trying to decode and invalid JSON String: " + json
8101                 });
8102             }
8103         };
8104     }();
8105
8106 })();
8107 /**
8108  * Shorthand for {@link Ext.JSON#encode}
8109  * @param {Mixed} o The variable to encode
8110  * @return {String} The JSON string
8111  * @member Ext
8112  * @method encode
8113  */
8114 Ext.encode = Ext.JSON.encode;
8115 /**
8116  * Shorthand for {@link Ext.JSON#decode}
8117  * @param {String} json The JSON string
8118  * @param {Boolean} safe (optional) Whether to return null or throw an exception if the JSON is invalid.
8119  * @return {Object} The resulting object
8120  * @member Ext
8121  * @method decode
8122  */
8123 Ext.decode = Ext.JSON.decode;
8124
8125
8126 /**
8127  * @class Ext
8128
8129  The Ext namespace (global object) encapsulates all classes, singletons, and utility methods provided by Sencha's libraries.</p>
8130  Most user interface Components are at a lower level of nesting in the namespace, but many common utility functions are provided
8131  as direct properties of the Ext namespace.
8132
8133  Also many frequently used methods from other classes are provided as shortcuts within the Ext namespace.
8134  For example {@link Ext#getCmp Ext.getCmp} aliases {@link Ext.ComponentManager#get Ext.ComponentManager.get}.
8135
8136  Many applications are initiated with {@link Ext#onReady Ext.onReady} which is called once the DOM is ready.
8137  This ensures all scripts have been loaded, preventing dependency issues. For example
8138
8139      Ext.onReady(function(){
8140          new Ext.Component({
8141              renderTo: document.body,
8142              html: 'DOM ready!'
8143          });
8144      });
8145
8146 For more information about how to use the Ext classes, see
8147
8148 * <a href="http://www.sencha.com/learn/">The Learning Center</a>
8149 * <a href="http://www.sencha.com/learn/Ext_FAQ">The FAQ</a>
8150 * <a href="http://www.sencha.com/forum/">The forums</a>
8151
8152  * @singleton
8153  * @markdown
8154  */
8155 Ext.apply(Ext, {
8156     userAgent: navigator.userAgent.toLowerCase(),
8157     cache: {},
8158     idSeed: 1000,
8159     BLANK_IMAGE_URL : 'data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==',
8160     isStrict: document.compatMode == "CSS1Compat",
8161     windowId: 'ext-window',
8162     documentId: 'ext-document',
8163
8164     /**
8165      * True when the document is fully initialized and ready for action
8166      * @type Boolean
8167      */
8168     isReady: false,
8169
8170     /**
8171      * True to automatically uncache orphaned Ext.core.Elements periodically (defaults to true)
8172      * @type Boolean
8173      */
8174     enableGarbageCollector: true,
8175
8176     /**
8177      * True to automatically purge event listeners during garbageCollection (defaults to true).
8178      * @type Boolean
8179      */
8180     enableListenerCollection: true,
8181
8182     /**
8183      * Generates unique ids. If the element already has an id, it is unchanged
8184      * @param {Mixed} el (optional) The element to generate an id for
8185      * @param {String} prefix (optional) Id prefix (defaults "ext-gen")
8186      * @return {String} The generated Id.
8187      */
8188     id: function(el, prefix) {
8189         el = Ext.getDom(el, true) || {};
8190         if (el === document) {
8191             el.id = this.documentId;
8192         }
8193         else if (el === window) {
8194             el.id = this.windowId;
8195         }
8196         if (!el.id) {
8197             el.id = (prefix || "ext-gen") + (++Ext.idSeed);
8198         }
8199         return el.id;
8200     },
8201
8202     /**
8203      * Returns the current document body as an {@link Ext.core.Element}.
8204      * @return Ext.core.Element The document body
8205      */
8206     getBody: function() {
8207         return Ext.get(document.body || false);
8208     },
8209
8210     /**
8211      * Returns the current document head as an {@link Ext.core.Element}.
8212      * @return Ext.core.Element The document head
8213      */
8214     getHead: function() {
8215         var head;
8216
8217         return function() {
8218             if (head == undefined) {
8219                 head = Ext.get(document.getElementsByTagName("head")[0]);
8220             }
8221
8222             return head;
8223         };
8224     }(),
8225
8226     /**
8227      * Returns the current HTML document object as an {@link Ext.core.Element}.
8228      * @return Ext.core.Element The document
8229      */
8230     getDoc: function() {
8231         return Ext.get(document);
8232     },
8233
8234     /**
8235      * This is shorthand reference to {@link Ext.ComponentManager#get}.
8236      * Looks up an existing {@link Ext.Component Component} by {@link Ext.Component#id id}
8237      * @param {String} id The component {@link Ext.Component#id id}
8238      * @return Ext.Component The Component, <tt>undefined</tt> if not found, or <tt>null</tt> if a
8239      * Class was found.
8240     */
8241     getCmp: function(id) {
8242         return Ext.ComponentManager.get(id);
8243     },
8244
8245     /**
8246      * Returns the current orientation of the mobile device
8247      * @return {String} Either 'portrait' or 'landscape'
8248      */
8249     getOrientation: function() {
8250         return window.innerHeight > window.innerWidth ? 'portrait' : 'landscape';
8251     },
8252
8253     /**
8254      * Attempts to destroy any objects passed to it by removing all event listeners, removing them from the
8255      * DOM (if applicable) and calling their destroy functions (if available).  This method is primarily
8256      * intended for arguments of type {@link Ext.core.Element} and {@link Ext.Component}, but any subclass of
8257      * {@link Ext.util.Observable} can be passed in.  Any number of elements and/or components can be
8258      * passed into this function in a single call as separate arguments.
8259      * @param {Mixed} arg1 An {@link Ext.core.Element}, {@link Ext.Component}, or an Array of either of these to destroy
8260      * @param {Mixed} arg2 (optional)
8261      * @param {Mixed} etc... (optional)
8262      */
8263     destroy: function() {
8264         var ln = arguments.length,
8265         i, arg;
8266
8267         for (i = 0; i < ln; i++) {
8268             arg = arguments[i];
8269             if (arg) {
8270                 if (Ext.isArray(arg)) {
8271                     this.destroy.apply(this, arg);
8272                 }
8273                 else if (Ext.isFunction(arg.destroy)) {
8274                     arg.destroy();
8275                 }
8276                 else if (arg.dom) {
8277                     arg.remove();
8278                 }
8279             }
8280         }
8281     },
8282
8283     /**
8284      * Execute a callback function in a particular scope. If no function is passed the call is ignored.
8285      * @param {Function} callback The callback to execute
8286      * @param {Object} scope (optional) The scope to execute in
8287      * @param {Array} args (optional) The arguments to pass to the function
8288      * @param {Number} delay (optional) Pass a number to delay the call by a number of milliseconds.
8289      */
8290     callback: function(callback, scope, args, delay){
8291         if(Ext.isFunction(callback)){
8292             args = args || [];
8293             scope = scope || window;
8294             if (delay) {
8295                 Ext.defer(callback, delay, scope, args);
8296             } else {
8297                 callback.apply(scope, args);
8298             }
8299         }
8300     },
8301
8302     /**
8303      * Convert certain characters (&, <, >, and ') to their HTML character equivalents for literal display in web pages.
8304      * @param {String} value The string to encode
8305      * @return {String} The encoded text
8306      */
8307     htmlEncode : function(value) {
8308         return Ext.String.htmlEncode(value);
8309     },
8310
8311     /**
8312      * Convert certain characters (&, <, >, and ') from their HTML character equivalents.
8313      * @param {String} value The string to decode
8314      * @return {String} The decoded text
8315      */
8316     htmlDecode : function(value) {
8317          return Ext.String.htmlDecode(value);
8318     },
8319
8320     /**
8321      * Appends content to the query string of a URL, handling logic for whether to place
8322      * a question mark or ampersand.
8323      * @param {String} url The URL to append to.
8324      * @param {String} s The content to append to the URL.
8325      * @return (String) The resulting URL
8326      */
8327     urlAppend : function(url, s) {
8328         if (!Ext.isEmpty(s)) {
8329             return url + (url.indexOf('?') === -1 ? '?' : '&') + s;
8330         }
8331         return url;
8332     }
8333 });
8334
8335
8336 Ext.ns = Ext.namespace;
8337
8338 // for old browsers
8339 window.undefined = window.undefined;
8340 /**
8341  * @class Ext
8342  * Ext core utilities and functions.
8343  * @singleton
8344  */
8345 (function(){
8346     var check = function(regex){
8347             return regex.test(Ext.userAgent);
8348         },
8349         docMode = document.documentMode,
8350         isOpera = check(/opera/),
8351         isOpera10_5 = isOpera && check(/version\/10\.5/),
8352         isChrome = check(/\bchrome\b/),
8353         isWebKit = check(/webkit/),
8354         isSafari = !isChrome && check(/safari/),
8355         isSafari2 = isSafari && check(/applewebkit\/4/), // unique to Safari 2
8356         isSafari3 = isSafari && check(/version\/3/),
8357         isSafari4 = isSafari && check(/version\/4/),
8358         isIE = !isOpera && check(/msie/),
8359         isIE7 = isIE && (check(/msie 7/) || docMode == 7),
8360         isIE8 = isIE && (check(/msie 8/) && docMode != 7 && docMode != 9 || docMode == 8),
8361         isIE9 = isIE && (check(/msie 9/) && docMode != 7 && docMode != 8 || docMode == 9),
8362         isIE6 = isIE && check(/msie 6/),
8363         isGecko = !isWebKit && check(/gecko/),
8364         isGecko3 = isGecko && check(/rv:1\.9/),
8365         isGecko4 = isGecko && check(/rv:2\.0/),
8366         isFF3_0 = isGecko3 && check(/rv:1\.9\.0/),
8367         isFF3_5 = isGecko3 && check(/rv:1\.9\.1/),
8368         isFF3_6 = isGecko3 && check(/rv:1\.9\.2/),
8369         isWindows = check(/windows|win32/),
8370         isMac = check(/macintosh|mac os x/),
8371         isLinux = check(/linux/),
8372         scrollWidth = null;
8373
8374     // remove css image flicker
8375     try {
8376         document.execCommand("BackgroundImageCache", false, true);
8377     } catch(e) {}
8378
8379     Ext.setVersion('extjs', '4.0.0');
8380     Ext.apply(Ext, {
8381         /**
8382          * URL to a blank file used by Ext when in secure mode for iframe src and onReady src to prevent
8383          * the IE insecure content warning (<tt>'about:blank'</tt>, except for IE in secure mode, which is <tt>'javascript:""'</tt>).
8384          * @type String
8385          */
8386         SSL_SECURE_URL : Ext.isSecure && isIE ? 'javascript:""' : 'about:blank',
8387
8388         /**
8389          * True if the {@link Ext.fx.Anim} Class is available
8390          * @type Boolean
8391          * @property enableFx
8392          */
8393
8394         /**
8395          * True to scope the reset CSS to be just applied to Ext components. Note that this wraps root containers
8396          * with an additional element. Also remember that when you turn on this option, you have to use ext-all-scoped {
8397          * unless you use the bootstrap.js to load your javascript, in which case it will be handled for you.
8398          * @type Boolean
8399          */
8400         scopeResetCSS : Ext.buildSettings.scopeResetCSS,
8401
8402         /**
8403          * EXPERIMENTAL - True to cascade listener removal to child elements when an element is removed.
8404          * Currently not optimized for performance.
8405          * @type Boolean
8406          */
8407         enableNestedListenerRemoval : false,
8408
8409         /**
8410          * Indicates whether to use native browser parsing for JSON methods.
8411          * This option is ignored if the browser does not support native JSON methods.
8412          * <b>Note: Native JSON methods will not work with objects that have functions.
8413          * Also, property names must be quoted, otherwise the data will not parse.</b> (Defaults to false)
8414          * @type Boolean
8415          */
8416         USE_NATIVE_JSON : false,
8417
8418         /**
8419          * Return the dom node for the passed String (id), dom node, or Ext.core.Element.
8420          * Optional 'strict' flag is needed for IE since it can return 'name' and
8421          * 'id' elements by using getElementById.
8422          * Here are some examples:
8423          * <pre><code>
8424 // gets dom node based on id
8425 var elDom = Ext.getDom('elId');
8426 // gets dom node based on the dom node
8427 var elDom1 = Ext.getDom(elDom);
8428
8429 // If we don&#39;t know if we are working with an
8430 // Ext.core.Element or a dom node use Ext.getDom
8431 function(el){
8432     var dom = Ext.getDom(el);
8433     // do something with the dom node
8434 }
8435          * </code></pre>
8436          * <b>Note</b>: the dom node to be found actually needs to exist (be rendered, etc)
8437          * when this method is called to be successful.
8438          * @param {Mixed} el
8439          * @return HTMLElement
8440          */
8441         getDom : function(el, strict) {
8442             if (!el || !document) {
8443                 return null;
8444             }
8445             if (el.dom) {
8446                 return el.dom;
8447             } else {
8448                 if (typeof el == 'string') {
8449                     var e = document.getElementById(el);
8450                     // IE returns elements with the 'name' and 'id' attribute.
8451                     // we do a strict check to return the element with only the id attribute
8452                     if (e && isIE && strict) {
8453                         if (el == e.getAttribute('id')) {
8454                             return e;
8455                         } else {
8456                             return null;
8457                         }
8458                     }
8459                     return e;
8460                 } else {
8461                     return el;
8462                 }
8463             }
8464         },
8465
8466         /**
8467          * Removes a DOM node from the document.
8468          * <p>Removes this element from the document, removes all DOM event listeners, and deletes the cache reference.
8469          * All DOM event listeners are removed from this element. If {@link Ext#enableNestedListenerRemoval Ext.enableNestedListenerRemoval} is
8470          * <code>true</code>, then DOM event listeners are also removed from all child nodes. The body node
8471          * will be ignored if passed in.</p>
8472          * @param {HTMLElement} node The node to remove
8473          */
8474         removeNode : isIE6 || isIE7 ? function() {
8475             var d;
8476             return function(n){
8477                 if(n && n.tagName != 'BODY'){
8478                     (Ext.enableNestedListenerRemoval) ? Ext.EventManager.purgeElement(n) : Ext.EventManager.removeAll(n);
8479                     d = d || document.createElement('div');
8480                     d.appendChild(n);
8481                     d.innerHTML = '';
8482                     delete Ext.cache[n.id];
8483                 }
8484             };
8485         }() : function(n) {
8486             if (n && n.parentNode && n.tagName != 'BODY') {
8487                 (Ext.enableNestedListenerRemoval) ? Ext.EventManager.purgeElement(n) : Ext.EventManager.removeAll(n);
8488                 n.parentNode.removeChild(n);
8489                 delete Ext.cache[n.id];
8490             }
8491         },
8492
8493         /**
8494          * True if the detected browser is Opera.
8495          * @type Boolean
8496          */
8497         isOpera : isOpera,
8498
8499         /**
8500          * True if the detected browser is Opera 10.5x.
8501          * @type Boolean
8502          */
8503         isOpera10_5 : isOpera10_5,
8504
8505         /**
8506          * True if the detected browser uses WebKit.
8507          * @type Boolean
8508          */
8509         isWebKit : isWebKit,
8510
8511         /**
8512          * True if the detected browser is Chrome.
8513          * @type Boolean
8514          */
8515         isChrome : isChrome,
8516
8517         /**
8518          * True if the detected browser is Safari.
8519          * @type Boolean
8520          */
8521         isSafari : isSafari,
8522
8523         /**
8524          * True if the detected browser is Safari 3.x.
8525          * @type Boolean
8526          */
8527         isSafari3 : isSafari3,
8528
8529         /**
8530          * True if the detected browser is Safari 4.x.
8531          * @type Boolean
8532          */
8533         isSafari4 : isSafari4,
8534
8535         /**
8536          * True if the detected browser is Safari 2.x.
8537          * @type Boolean
8538          */
8539         isSafari2 : isSafari2,
8540
8541         /**
8542          * True if the detected browser is Internet Explorer.
8543          * @type Boolean
8544          */
8545         isIE : isIE,
8546
8547         /**
8548          * True if the detected browser is Internet Explorer 6.x.
8549          * @type Boolean
8550          */
8551         isIE6 : isIE6,
8552
8553         /**
8554          * True if the detected browser is Internet Explorer 7.x.
8555          * @type Boolean
8556          */
8557         isIE7 : isIE7,
8558
8559         /**
8560          * True if the detected browser is Internet Explorer 8.x.
8561          * @type Boolean
8562          */
8563         isIE8 : isIE8,
8564
8565         /**
8566          * True if the detected browser is Internet Explorer 9.x.
8567          * @type Boolean
8568          */
8569         isIE9 : isIE9,
8570
8571         /**
8572          * True if the detected browser uses the Gecko layout engine (e.g. Mozilla, Firefox).
8573          * @type Boolean
8574          */
8575         isGecko : isGecko,
8576
8577         /**
8578          * True if the detected browser uses a Gecko 1.9+ layout engine (e.g. Firefox 3.x).
8579          * @type Boolean
8580          */
8581         isGecko3 : isGecko3,
8582
8583         /**
8584          * True if the detected browser uses a Gecko 2.0+ layout engine (e.g. Firefox 4.x).
8585          * @type Boolean
8586          */
8587         isGecko4 : isGecko4,
8588
8589         /**
8590          * True if the detected browser uses FireFox 3.0
8591          * @type Boolean
8592          */
8593
8594         isFF3_0 : isFF3_0,
8595         /**
8596          * True if the detected browser uses FireFox 3.5
8597          * @type Boolean
8598          */
8599
8600         isFF3_5 : isFF3_5,
8601         /**
8602          * True if the detected browser uses FireFox 3.6
8603          * @type Boolean
8604          */
8605         isFF3_6 : isFF3_6,
8606
8607         /**
8608          * True if the detected platform is Linux.
8609          * @type Boolean
8610          */
8611         isLinux : isLinux,
8612
8613         /**
8614          * True if the detected platform is Windows.
8615          * @type Boolean
8616          */
8617         isWindows : isWindows,
8618
8619         /**
8620          * True if the detected platform is Mac OS.
8621          * @type Boolean
8622          */
8623         isMac : isMac,
8624
8625         /**
8626          * URL to a 1x1 transparent gif image used by Ext to create inline icons with CSS background images.
8627          * In older versions of IE, this defaults to "http://sencha.com/s.gif" and you should change this to a URL on your server.
8628          * For other browsers it uses an inline data URL.
8629          * @type String
8630          */
8631         BLANK_IMAGE_URL : (isIE6 || isIE7) ? 'http:/' + '/www.sencha.com/s.gif' : 'data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==',
8632
8633         /**
8634          * <p>Utility method for returning a default value if the passed value is empty.</p>
8635          * <p>The value is deemed to be empty if it is<div class="mdetail-params"><ul>
8636          * <li>null</li>
8637          * <li>undefined</li>
8638          * <li>an empty array</li>
8639          * <li>a zero length string (Unless the <tt>allowBlank</tt> parameter is <tt>true</tt>)</li>
8640          * </ul></div>
8641          * @param {Mixed} value The value to test
8642          * @param {Mixed} defaultValue The value to return if the original value is empty
8643          * @param {Boolean} allowBlank (optional) true to allow zero length strings to qualify as non-empty (defaults to false)
8644          * @return {Mixed} value, if non-empty, else defaultValue
8645          * @deprecated 4.0.0 Use {Ext#valueFrom} instead
8646          */
8647         value : function(v, defaultValue, allowBlank){
8648             return Ext.isEmpty(v, allowBlank) ? defaultValue : v;
8649         },
8650
8651         /**
8652          * Escapes the passed string for use in a regular expression
8653          * @param {String} str
8654          * @return {String}
8655          * @deprecated 4.0.0 Use {@link Ext.String#escapeRegex} instead
8656          */
8657         escapeRe : function(s) {
8658             return s.replace(/([-.*+?^${}()|[\]\/\\])/g, "\\$1");
8659         },
8660
8661         /**
8662          * Applies event listeners to elements by selectors when the document is ready.
8663          * The event name is specified with an <tt>&#64;</tt> suffix.
8664          * <pre><code>
8665 Ext.addBehaviors({
8666     // add a listener for click on all anchors in element with id foo
8667     '#foo a&#64;click' : function(e, t){
8668         // do something
8669     },
8670
8671     // add the same listener to multiple selectors (separated by comma BEFORE the &#64;)
8672     '#foo a, #bar span.some-class&#64;mouseover' : function(){
8673         // do something
8674     }
8675 });
8676          * </code></pre>
8677          * @param {Object} obj The list of behaviors to apply
8678          */
8679         addBehaviors : function(o){
8680             if(!Ext.isReady){
8681                 Ext.onReady(function(){
8682                     Ext.addBehaviors(o);
8683                 });
8684             } else {
8685                 var cache = {}, // simple cache for applying multiple behaviors to same selector does query multiple times
8686                     parts,
8687                     b,
8688                     s;
8689                 for (b in o) {
8690                     if ((parts = b.split('@'))[1]) { // for Object prototype breakers
8691                         s = parts[0];
8692                         if(!cache[s]){
8693                             cache[s] = Ext.select(s);
8694                         }
8695                         cache[s].on(parts[1], o[b]);
8696                     }
8697                 }
8698                 cache = null;
8699             }
8700         },
8701
8702         /**
8703          * Utility method for getting the width of the browser scrollbar. This can differ depending on
8704          * operating system settings, such as the theme or font size.
8705          * @param {Boolean} force (optional) true to force a recalculation of the value.
8706          * @return {Number} The width of the scrollbar.
8707          */
8708         getScrollBarWidth: function(force){
8709             if(!Ext.isReady){
8710                 return 0;
8711             }
8712
8713             if(force === true || scrollWidth === null){
8714                 // BrowserBug: IE9
8715                 // When IE9 positions an element offscreen via offsets, the offsetWidth is
8716                 // inaccurately reported. For IE9 only, we render on screen before removing.
8717                 var cssClass = Ext.isIE9 ? '' : Ext.baseCSSPrefix + 'hide-offsets';
8718                     // Append our div, do our calculation and then remove it
8719                 var div = Ext.getBody().createChild('<div class="' + cssClass + '" style="width:100px;height:50px;overflow:hidden;"><div style="height:200px;"></div></div>'),
8720                     child = div.child('div', true);
8721                 var w1 = child.offsetWidth;
8722                 div.setStyle('overflow', (Ext.isWebKit || Ext.isGecko) ? 'auto' : 'scroll');
8723                 var w2 = child.offsetWidth;
8724                 div.remove();
8725                 // Need to add 2 to ensure we leave enough space
8726                 scrollWidth = w1 - w2 + 2;
8727             }
8728             return scrollWidth;
8729         },
8730
8731         /**
8732          * Copies a set of named properties fom the source object to the destination object.
8733          * <p>example:<pre><code>
8734 ImageComponent = Ext.extend(Ext.Component, {
8735     initComponent: function() {
8736         this.autoEl = { tag: 'img' };
8737         MyComponent.superclass.initComponent.apply(this, arguments);
8738         this.initialBox = Ext.copyTo({}, this.initialConfig, 'x,y,width,height');
8739     }
8740 });
8741          * </code></pre>
8742          * Important note: To borrow class prototype methods, use {@link Ext.Base#borrow} instead.
8743          * @param {Object} dest The destination object.
8744          * @param {Object} source The source object.
8745          * @param {Array/String} names Either an Array of property names, or a comma-delimited list
8746          * of property names to copy.
8747          * @param {Boolean} usePrototypeKeys (Optional) Defaults to false. Pass true to copy keys off of the prototype as well as the instance.
8748          * @return {Object} The modified object.
8749         */
8750         copyTo : function(dest, source, names, usePrototypeKeys){
8751             if(typeof names == 'string'){
8752                 names = names.split(/[,;\s]/);
8753             }
8754             Ext.each(names, function(name){
8755                 if(usePrototypeKeys || source.hasOwnProperty(name)){
8756                     dest[name] = source[name];
8757                 }
8758             }, this);
8759             return dest;
8760         },
8761
8762         /**
8763          * Attempts to destroy and then remove a set of named properties of the passed object.
8764          * @param {Object} o The object (most likely a Component) who's properties you wish to destroy.
8765          * @param {Mixed} arg1 The name of the property to destroy and remove from the object.
8766          * @param {Mixed} etc... More property names to destroy and remove.
8767          */
8768         destroyMembers : function(o, arg1, arg2, etc){
8769             for (var i = 1, a = arguments, len = a.length; i < len; i++) {
8770                 Ext.destroy(o[a[i]]);
8771                 delete o[a[i]];
8772             }
8773         },
8774
8775         /**
8776          * Partitions the set into two sets: a true set and a false set.
8777          * Example:
8778          * Example2:
8779          * <pre><code>
8780 // Example 1:
8781 Ext.partition([true, false, true, true, false]); // [[true, true, true], [false, false]]
8782
8783 // Example 2:
8784 Ext.partition(
8785     Ext.query("p"),
8786     function(val){
8787         return val.className == "class1"
8788     }
8789 );
8790 // true are those paragraph elements with a className of "class1",
8791 // false set are those that do not have that className.
8792          * </code></pre>
8793          * @param {Array|NodeList} arr The array to partition
8794          * @param {Function} truth (optional) a function to determine truth.  If this is omitted the element
8795          *                   itself must be able to be evaluated for its truthfulness.
8796          * @return {Array} [true<Array>,false<Array>]
8797          * @deprecated 4.0.0 Will be removed in the next major version
8798          */
8799         partition : function(arr, truth){
8800             var ret = [[],[]];
8801             Ext.each(arr, function(v, i, a) {
8802                 ret[ (truth && truth(v, i, a)) || (!truth && v) ? 0 : 1].push(v);
8803             });
8804             return ret;
8805         },
8806
8807         /**
8808          * Invokes a method on each item in an Array.
8809          * <pre><code>
8810 // Example:
8811 Ext.invoke(Ext.query("p"), "getAttribute", "id");
8812 // [el1.getAttribute("id"), el2.getAttribute("id"), ..., elN.getAttribute("id")]
8813          * </code></pre>
8814          * @param {Array|NodeList} arr The Array of items to invoke the method on.
8815          * @param {String} methodName The method name to invoke.
8816          * @param {...*} args Arguments to send into the method invocation.
8817          * @return {Array} The results of invoking the method on each item in the array.
8818          * @deprecated 4.0.0 Will be removed in the next major version
8819          */
8820         invoke : function(arr, methodName){
8821             var ret = [],
8822                 args = Array.prototype.slice.call(arguments, 2);
8823             Ext.each(arr, function(v,i) {
8824                 if (v && typeof v[methodName] == 'function') {
8825                     ret.push(v[methodName].apply(v, args));
8826                 } else {
8827                     ret.push(undefined);
8828                 }
8829             });
8830             return ret;
8831         },
8832
8833         /**
8834          * <p>Zips N sets together.</p>
8835          * <pre><code>
8836 // Example 1:
8837 Ext.zip([1,2,3],[4,5,6]); // [[1,4],[2,5],[3,6]]
8838 // Example 2:
8839 Ext.zip(
8840     [ "+", "-", "+"],
8841     [  12,  10,  22],
8842     [  43,  15,  96],
8843     function(a, b, c){
8844         return "$" + a + "" + b + "." + c
8845     }
8846 ); // ["$+12.43", "$-10.15", "$+22.96"]
8847          * </code></pre>
8848          * @param {Arrays|NodeLists} arr This argument may be repeated. Array(s) to contribute values.
8849          * @param {Function} zipper (optional) The last item in the argument list. This will drive how the items are zipped together.
8850          * @return {Array} The zipped set.
8851          * @deprecated 4.0.0 Will be removed in the next major version
8852          */
8853         zip : function(){
8854             var parts = Ext.partition(arguments, function( val ){ return typeof val != 'function'; }),
8855                 arrs = parts[0],
8856                 fn = parts[1][0],
8857                 len = Ext.max(Ext.pluck(arrs, "length")),
8858                 ret = [];
8859
8860             for (var i = 0; i < len; i++) {
8861                 ret[i] = [];
8862                 if(fn){
8863                     ret[i] = fn.apply(fn, Ext.pluck(arrs, i));
8864                 }else{
8865                     for (var j = 0, aLen = arrs.length; j < aLen; j++){
8866                         ret[i].push( arrs[j][i] );
8867                     }
8868                 }
8869             }
8870             return ret;
8871         },
8872
8873         /**
8874          * Turns an array into a sentence, joined by a specified connector - e.g.:
8875          * Ext.toSentence(['Adama', 'Tigh', 'Roslin']); //'Adama, Tigh and Roslin'
8876          * Ext.toSentence(['Adama', 'Tigh', 'Roslin'], 'or'); //'Adama, Tigh or Roslin'
8877          * @param {Array} items The array to create a sentence from
8878          * @param {String} connector The string to use to connect the last two words. Usually 'and' or 'or' - defaults to 'and'.
8879          * @return {String} The sentence string
8880          * @deprecated 4.0.0 Will be removed in the next major version
8881          */
8882         toSentence: function(items, connector) {
8883             var length = items.length;
8884
8885             if (length <= 1) {
8886                 return items[0];
8887             } else {
8888                 var head = items.slice(0, length - 1),
8889                     tail = items[length - 1];
8890
8891                 return Ext.util.Format.format("{0} {1} {2}", head.join(", "), connector || 'and', tail);
8892             }
8893         },
8894
8895         /**
8896          * By default, Ext intelligently decides whether floating elements should be shimmed. If you are using flash,
8897          * you may want to set this to true.
8898          * @type Boolean
8899          */
8900         useShims: isIE6
8901     });
8902 })();
8903
8904 /**
8905  * TBD
8906  * @type Function
8907  * @param {Object} config
8908  */
8909 Ext.application = function(config) {
8910     Ext.require('Ext.app.Application');
8911
8912     Ext.onReady(function() {
8913         Ext.create('Ext.app.Application', config);
8914     });
8915 };
8916
8917 /**
8918  * @class Ext.util.Format
8919
8920 This class is a centralized place for formatting functions inside the library. It includes
8921 functions to format various different types of data, such as text, dates and numeric values.
8922
8923 __Localization__
8924 This class contains several options for localization. These can be set once the library has loaded,
8925 all calls to the functions from that point will use the locale settings that were specified.
8926 Options include:
8927 - thousandSeparator
8928 - decimalSeparator
8929 - currenyPrecision
8930 - currencySign
8931 - currencyAtEnd
8932 This class also uses the default date format defined here: {@link Ext.date#defaultFormat}.
8933
8934 __Using with renderers__
8935 There are two helper functions that return a new function that can be used in conjunction with 
8936 grid renderers:
8937
8938     columns: [{
8939         dataIndex: 'date',
8940         renderer: Ext.util.Format.dateRenderer('Y-m-d')
8941     }, {
8942         dataIndex: 'time',
8943         renderer: Ext.util.Format.numberRenderer('0.000')
8944     }]
8945     
8946 Functions that only take a single argument can also be passed directly:
8947     columns: [{
8948         dataIndex: 'cost',
8949         renderer: Ext.util.Format.usMoney
8950     }, {
8951         dataIndex: 'productCode',
8952         renderer: Ext.util.Format.uppercase
8953     }]
8954     
8955 __Using with XTemplates__
8956 XTemplates can also directly use Ext.util.Format functions:
8957
8958     new Ext.XTemplate([
8959         'Date: {startDate:date("Y-m-d")}',
8960         'Cost: {cost:usMoney}'
8961     ]);
8962
8963  * @markdown
8964  * @singleton
8965  */
8966 (function() {
8967     Ext.ns('Ext.util');
8968
8969     Ext.util.Format = {};
8970     var UtilFormat     = Ext.util.Format,
8971         stripTagsRE    = /<\/?[^>]+>/gi,
8972         stripScriptsRe = /(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)/ig,
8973         nl2brRe        = /\r?\n/g,
8974
8975         // A RegExp to remove from a number format string, all characters except digits and '.'
8976         formatCleanRe  = /[^\d\.]/g,
8977
8978         // A RegExp to remove from a number format string, all characters except digits and the local decimal separator.
8979         // Created on first use. The local decimal separator character must be initialized for this to be created.
8980         I18NFormatCleanRe;
8981
8982     Ext.apply(UtilFormat, {
8983         /**
8984          * @type String
8985          * @property thousandSeparator
8986          * <p>The character that the {@link #number} function uses as a thousand separator.</p>
8987          * <p>This defaults to <code>,</code>, but may be overridden in a locale file.</p>
8988          */
8989         thousandSeparator: ',',
8990
8991         /**
8992          * @type String
8993          * @property decimalSeparator
8994          * <p>The character that the {@link #number} function uses as a decimal point.</p>
8995          * <p>This defaults to <code>.</code>, but may be overridden in a locale file.</p>
8996          */
8997         decimalSeparator: '.',
8998
8999         /**
9000          * @type Number
9001          * @property currencyPrecision
9002          * <p>The number of decimal places that the {@link #currency} function displays.</p>
9003          * <p>This defaults to <code>2</code>, but may be overridden in a locale file.</p>
9004          */
9005         currencyPrecision: 2,
9006
9007         /**
9008          * @type String
9009          * @property currencySign
9010          * <p>The currency sign that the {@link #currency} function displays.</p>
9011          * <p>This defaults to <code>$</code>, but may be overridden in a locale file.</p>
9012          */
9013         currencySign: '$',
9014
9015         /**
9016          * @type Boolean
9017          * @property currencyAtEnd
9018          * <p>This may be set to <code>true</code> to make the {@link #currency} function
9019          * append the currency sign to the formatted value.</p>
9020          * <p>This defaults to <code>false</code>, but may be overridden in a locale file.</p>
9021          */
9022         currencyAtEnd: false,
9023
9024         /**
9025          * Checks a reference and converts it to empty string if it is undefined
9026          * @param {Mixed} value Reference to check
9027          * @return {Mixed} Empty string if converted, otherwise the original value
9028          */
9029         undef : function(value) {
9030             return value !== undefined ? value : "";
9031         },
9032
9033         /**
9034          * Checks a reference and converts it to the default value if it's empty
9035          * @param {Mixed} value Reference to check
9036          * @param {String} defaultValue The value to insert of it's undefined (defaults to "")
9037          * @return {String}
9038          */
9039         defaultValue : function(value, defaultValue) {
9040             return value !== undefined && value !== '' ? value : defaultValue;
9041         },
9042
9043         /**
9044          * Returns a substring from within an original string
9045          * @param {String} value The original text
9046          * @param {Number} start The start index of the substring
9047          * @param {Number} length The length of the substring
9048          * @return {String} The substring
9049          */
9050         substr : function(value, start, length) {
9051             return String(value).substr(start, length);
9052         },
9053
9054         /**
9055          * Converts a string to all lower case letters
9056          * @param {String} value The text to convert
9057          * @return {String} The converted text
9058          */
9059         lowercase : function(value) {
9060             return String(value).toLowerCase();
9061         },
9062
9063         /**
9064          * Converts a string to all upper case letters
9065          * @param {String} value The text to convert
9066          * @return {String} The converted text
9067          */
9068         uppercase : function(value) {
9069             return String(value).toUpperCase();
9070         },
9071
9072         /**
9073          * Format a number as US currency
9074          * @param {Number/String} value The numeric value to format
9075          * @return {String} The formatted currency string
9076          */
9077         usMoney : function(v) {
9078             return UtilFormat.currency(v, '$', 2);
9079         },
9080
9081         /**
9082          * Format a number as a currency
9083          * @param {Number/String} value The numeric value to format
9084          * @param {String} sign The currency sign to use (defaults to {@link #currencySign})
9085          * @param {Number} decimals The number of decimals to use for the currency (defaults to {@link #currencyPrecision})
9086          * @param {Boolean} end True if the currency sign should be at the end of the string (defaults to {@link #currencyAtEnd})
9087          * @return {String} The formatted currency string
9088          */
9089         currency: function(v, currencySign, decimals, end) {
9090             var negativeSign = '',
9091                 format = ",0",
9092                 i = 0;
9093             v = v - 0;
9094             if (v < 0) {
9095                 v = -v;
9096                 negativeSign = '-';
9097             }
9098             decimals = decimals || UtilFormat.currencyPrecision;
9099             format += format + (decimals > 0 ? '.' : '');
9100             for (; i < decimals; i++) {
9101                 format += '0';
9102             }
9103             v = UtilFormat.number(v, format); 
9104             if ((end || UtilFormat.currencyAtEnd) === true) {
9105                 return Ext.String.format("{0}{1}{2}", negativeSign, v, currencySign || UtilFormat.currencySign);
9106             } else {
9107                 return Ext.String.format("{0}{1}{2}", negativeSign, currencySign || UtilFormat.currencySign, v);
9108             }
9109         },
9110
9111         /**
9112          * Formats the passed date using the specified format pattern.
9113          * @param {String/Date} value The value to format. If a string is passed, it is converted to a Date by the Javascript
9114          * Date object's <a href="http://www.w3schools.com/jsref/jsref_parse.asp">parse()</a> method.
9115          * @param {String} format (Optional) Any valid date format string. Defaults to {@link Ext.Date#defaultFormat}.
9116          * @return {String} The formatted date string.
9117          */
9118         date: function(v, format) {
9119             if (!v) {
9120                 return "";
9121             }
9122             if (!Ext.isDate(v)) {
9123                 v = new Date(Date.parse(v));
9124             }
9125             return Ext.Date.dateFormat(v, format || Ext.Date.defaultFormat);
9126         },
9127
9128         /**
9129          * Returns a date rendering function that can be reused to apply a date format multiple times efficiently
9130          * @param {String} format Any valid date format string. Defaults to {@link Ext.Date#defaultFormat}.
9131          * @return {Function} The date formatting function
9132          */
9133         dateRenderer : function(format) {
9134             return function(v) {
9135                 return UtilFormat.date(v, format);
9136             };
9137         },
9138
9139         /**
9140          * Strips all HTML tags
9141          * @param {Mixed} value The text from which to strip tags
9142          * @return {String} The stripped text
9143          */
9144         stripTags : function(v) {
9145             return !v ? v : String(v).replace(stripTagsRE, "");
9146         },
9147
9148         /**
9149          * Strips all script tags
9150          * @param {Mixed} value The text from which to strip script tags
9151          * @return {String} The stripped text
9152          */
9153         stripScripts : function(v) {
9154             return !v ? v : String(v).replace(stripScriptsRe, "");
9155         },
9156
9157         /**
9158          * Simple format for a file size (xxx bytes, xxx KB, xxx MB)
9159          * @param {Number/String} size The numeric value to format
9160          * @return {String} The formatted file size
9161          */
9162         fileSize : function(size) {
9163             if (size < 1024) {
9164                 return size + " bytes";
9165             } else if (size < 1048576) {
9166                 return (Math.round(((size*10) / 1024))/10) + " KB";
9167             } else {
9168                 return (Math.round(((size*10) / 1048576))/10) + " MB";
9169             }
9170         },
9171
9172         /**
9173          * It does simple math for use in a template, for example:<pre><code>
9174          * var tpl = new Ext.Template('{value} * 10 = {value:math("* 10")}');
9175          * </code></pre>
9176          * @return {Function} A function that operates on the passed value.
9177          */
9178         math : function(){
9179             var fns = {};
9180
9181             return function(v, a){
9182                 if (!fns[a]) {
9183                     fns[a] = Ext.functionFactory('v', 'return v ' + a + ';');
9184                 }
9185                 return fns[a](v);
9186             };
9187         }(),
9188
9189         /**
9190          * Rounds the passed number to the required decimal precision.
9191          * @param {Number/String} value The numeric value to round.
9192          * @param {Number} precision The number of decimal places to which to round the first parameter's value.
9193          * @return {Number} The rounded value.
9194          */
9195         round : function(value, precision) {
9196             var result = Number(value);
9197             if (typeof precision == 'number') {
9198                 precision = Math.pow(10, precision);
9199                 result = Math.round(value * precision) / precision;
9200             }
9201             return result;
9202         },
9203
9204         /**
9205          * <p>Formats the passed number according to the passed format string.</p>
9206          * <p>The number of digits after the decimal separator character specifies the number of
9207          * decimal places in the resulting string. The <u>local-specific</u> decimal character is used in the result.</p>
9208          * <p>The <i>presence</i> of a thousand separator character in the format string specifies that
9209          * the <u>locale-specific</u> thousand separator (if any) is inserted separating thousand groups.</p>
9210          * <p>By default, "," is expected as the thousand separator, and "." is expected as the decimal separator.</p>
9211          * <p><b>New to Ext4</b></p>
9212          * <p>Locale-specific characters are always used in the formatted output when inserting
9213          * thousand and decimal separators.</p>
9214          * <p>The format string must specify separator characters according to US/UK conventions ("," as the
9215          * thousand separator, and "." as the decimal separator)</p>
9216          * <p>To allow specification of format strings according to local conventions for separator characters, add
9217          * the string <code>/i</code> to the end of the format string.</p>
9218          * <div style="margin-left:40px">examples (123456.789):
9219          * <div style="margin-left:10px">
9220          * 0 - (123456) show only digits, no precision<br>
9221          * 0.00 - (123456.78) show only digits, 2 precision<br>
9222          * 0.0000 - (123456.7890) show only digits, 4 precision<br>
9223          * 0,000 - (123,456) show comma and digits, no precision<br>
9224          * 0,000.00 - (123,456.78) show comma and digits, 2 precision<br>
9225          * 0,0.00 - (123,456.78) shortcut method, show comma and digits, 2 precision<br>
9226          * To allow specification of the formatting string using UK/US grouping characters (,) and decimal (.) for international numbers, add /i to the end.
9227          * For example: 0.000,00/i
9228          * </div></div>
9229          * @param {Number} v The number to format.
9230          * @param {String} format The way you would like to format this text.
9231          * @return {String} The formatted number.
9232          */
9233         number:
9234             function(v, formatString) {
9235             if (!formatString) {
9236                 return v;
9237             }
9238             v = Ext.Number.from(v, NaN);
9239             if (isNaN(v)) {
9240                 return '';
9241             }
9242             var comma = UtilFormat.thousandSeparator,
9243                 dec   = UtilFormat.decimalSeparator,
9244                 i18n  = false,
9245                 neg   = v < 0,
9246                 hasComma,
9247                 psplit;
9248
9249             v = Math.abs(v);
9250
9251             // The "/i" suffix allows caller to use a locale-specific formatting string.
9252             // Clean the format string by removing all but numerals and the decimal separator.
9253             // Then split the format string into pre and post decimal segments according to *what* the
9254             // decimal separator is. If they are specifying "/i", they are using the local convention in the format string.
9255             if (formatString.substr(formatString.length - 2) == '/i') {
9256                 if (!I18NFormatCleanRe) {
9257                     I18NFormatCleanRe = new RegExp('[^\\d\\' + UtilFormat.decimalSeparator + ']','g');
9258                 }
9259                 formatString = formatString.substr(0, formatString.length - 2);
9260                 i18n   = true;
9261                 hasComma = formatString.indexOf(comma) != -1;
9262                 psplit = formatString.replace(I18NFormatCleanRe, '').split(dec);
9263             } else {
9264                 hasComma = formatString.indexOf(',') != -1;
9265                 psplit = formatString.replace(formatCleanRe, '').split('.');
9266             }
9267
9268             if (1 < psplit.length) {
9269                 v = v.toFixed(psplit[1].length);
9270             } else if(2 < psplit.length) {
9271                 Ext.Error.raise({
9272                     sourceClass: "Ext.util.Format",
9273                     sourceMethod: "number",
9274                     value: v,
9275                     formatString: formatString,
9276                     msg: "Invalid number format, should have no more than 1 decimal"
9277                 });
9278             } else {
9279                 v = v.toFixed(0);
9280             }
9281
9282             var fnum = v.toString();
9283
9284             psplit = fnum.split('.');
9285
9286             if (hasComma) {
9287                 var cnum = psplit[0],
9288                     parr = [],
9289                     j    = cnum.length,
9290                     m    = Math.floor(j / 3),
9291                     n    = cnum.length % 3 || 3,
9292                     i;
9293
9294                 for (i = 0; i < j; i += n) {
9295                     if (i !== 0) {
9296                         n = 3;
9297                     }
9298
9299                     parr[parr.length] = cnum.substr(i, n);
9300                     m -= 1;
9301                 }
9302                 fnum = parr.join(comma);
9303                 if (psplit[1]) {
9304                     fnum += dec + psplit[1];
9305                 }
9306             } else {
9307                 if (psplit[1]) {
9308                     fnum = psplit[0] + dec + psplit[1];
9309                 }
9310             }
9311
9312             return (neg ? '-' : '') + formatString.replace(/[\d,?\.?]+/, fnum);
9313         },
9314
9315         /**
9316          * Returns a number rendering function that can be reused to apply a number format multiple times efficiently
9317          * @param {String} format Any valid number format string for {@link #number}
9318          * @return {Function} The number formatting function
9319          */
9320         numberRenderer : function(format) {
9321             return function(v) {
9322                 return UtilFormat.number(v, format);
9323             };
9324         },
9325
9326         /**
9327          * Selectively do a plural form of a word based on a numeric value. For example, in a template,
9328          * {commentCount:plural("Comment")}  would result in "1 Comment" if commentCount was 1 or would be "x Comments"
9329          * if the value is 0 or greater than 1.
9330          * @param {Number} value The value to compare against
9331          * @param {String} singular The singular form of the word
9332          * @param {String} plural (optional) The plural form of the word (defaults to the singular with an "s")
9333          */
9334         plural : function(v, s, p) {
9335             return v +' ' + (v == 1 ? s : (p ? p : s+'s'));
9336         },
9337
9338         /**
9339          * Converts newline characters to the HTML tag &lt;br/>
9340          * @param {String} The string value to format.
9341          * @return {String} The string with embedded &lt;br/> tags in place of newlines.
9342          */
9343         nl2br : function(v) {
9344             return Ext.isEmpty(v) ? '' : v.replace(nl2brRe, '<br/>');
9345         },
9346
9347         /**
9348          * Capitalize the given string. See {@link Ext.String#capitalize}.
9349          */
9350         capitalize: Ext.String.capitalize,
9351
9352         /**
9353          * Truncate a string and add an ellipsis ('...') to the end if it exceeds the specified length.
9354          * See {@link Ext.String#ellipsis}.
9355          */
9356         ellipsis: Ext.String.ellipsis,
9357
9358         /**
9359          * Formats to a string. See {@link Ext.String#format}
9360          */
9361         format: Ext.String.format,
9362
9363         /**
9364          * Convert certain characters (&, <, >, and ') from their HTML character equivalents.
9365          * See {@link Ext.string#htmlDecode}.
9366          */
9367         htmlDecode: Ext.String.htmlDecode,
9368
9369         /**
9370          * Convert certain characters (&, <, >, and ') to their HTML character equivalents for literal display in web pages.
9371          * See {@link Ext.String#htmlEncode}.
9372          */
9373         htmlEncode: Ext.String.htmlEncode,
9374
9375         /**
9376          * Adds left padding to a string. See {@link Ext.String#leftPad}
9377          */
9378         leftPad: Ext.String.leftPad,
9379
9380         /**
9381          * Trims any whitespace from either side of a string. See {@link Ext.String#trim}.
9382          */
9383         trim : Ext.String.trim,
9384
9385         /**
9386          * Parses a number or string representing margin sizes into an object. Supports CSS-style margin declarations
9387          * (e.g. 10, "10", "10 10", "10 10 10" and "10 10 10 10" are all valid options and would return the same result)
9388          * @param {Number|String} v The encoded margins
9389          * @return {Object} An object with margin sizes for top, right, bottom and left
9390          */
9391         parseBox : function(box) {
9392             if (Ext.isNumber(box)) {
9393                 box = box.toString();
9394             }
9395             var parts  = box.split(' '),
9396                 ln = parts.length;
9397
9398             if (ln == 1) {
9399                 parts[1] = parts[2] = parts[3] = parts[0];
9400             }
9401             else if (ln == 2) {
9402                 parts[2] = parts[0];
9403                 parts[3] = parts[1];
9404             }
9405             else if (ln == 3) {
9406                 parts[3] = parts[1];
9407             }
9408
9409             return {
9410                 top   :parseInt(parts[0], 10) || 0,
9411                 right :parseInt(parts[1], 10) || 0,
9412                 bottom:parseInt(parts[2], 10) || 0,
9413                 left  :parseInt(parts[3], 10) || 0
9414             };
9415         },
9416
9417         /**
9418          * Escapes the passed string for use in a regular expression
9419          * @param {String} str
9420          * @return {String}
9421          */
9422         escapeRegex : function(s) {
9423             return s.replace(/([\-.*+?\^${}()|\[\]\/\\])/g, "\\$1");
9424         }
9425     });
9426 })();
9427
9428 /**
9429  * @class Ext.util.TaskRunner
9430  * Provides the ability to execute one or more arbitrary tasks in a multithreaded
9431  * manner.  Generally, you can use the singleton {@link Ext.TaskManager} instead, but
9432  * if needed, you can create separate instances of TaskRunner.  Any number of
9433  * separate tasks can be started at any time and will run independently of each
9434  * other. Example usage:
9435  * <pre><code>
9436 // Start a simple clock task that updates a div once per second
9437 var updateClock = function(){
9438     Ext.fly('clock').update(new Date().format('g:i:s A'));
9439
9440 var task = {
9441     run: updateClock,
9442     interval: 1000 //1 second
9443 }
9444 var runner = new Ext.util.TaskRunner();
9445 runner.start(task);
9446
9447 // equivalent using TaskManager
9448 Ext.TaskManager.start({
9449     run: updateClock,
9450     interval: 1000
9451 });
9452
9453  * </code></pre>
9454  * <p>See the {@link #start} method for details about how to configure a task object.</p>
9455  * Also see {@link Ext.util.DelayedTask}. 
9456  * 
9457  * @constructor
9458  * @param {Number} interval (optional) The minimum precision in milliseconds supported by this TaskRunner instance
9459  * (defaults to 10)
9460  */
9461 Ext.ns('Ext.util');
9462
9463 Ext.util.TaskRunner = function(interval) {
9464     interval = interval || 10;
9465     var tasks = [],
9466     removeQueue = [],
9467     id = 0,
9468     running = false,
9469
9470     // private
9471     stopThread = function() {
9472         running = false;
9473         clearInterval(id);
9474         id = 0;
9475     },
9476
9477     // private
9478     startThread = function() {
9479         if (!running) {
9480             running = true;
9481             id = setInterval(runTasks, interval);
9482         }
9483     },
9484
9485     // private
9486     removeTask = function(t) {
9487         removeQueue.push(t);
9488         if (t.onStop) {
9489             t.onStop.apply(t.scope || t);
9490         }
9491     },
9492
9493     // private
9494     runTasks = function() {
9495         var rqLen = removeQueue.length,
9496             now = new Date().getTime(),
9497             i;
9498
9499         if (rqLen > 0) {
9500             for (i = 0; i < rqLen; i++) {
9501                 Ext.Array.remove(tasks, removeQueue[i]);
9502             }
9503             removeQueue = [];
9504             if (tasks.length < 1) {
9505                 stopThread();
9506                 return;
9507             }
9508         }
9509         i = 0;
9510         var t,
9511             itime,
9512             rt,
9513             len = tasks.length;
9514         for (; i < len; ++i) {
9515             t = tasks[i];
9516             itime = now - t.taskRunTime;
9517             if (t.interval <= itime) {
9518                 rt = t.run.apply(t.scope || t, t.args || [++t.taskRunCount]);
9519                 t.taskRunTime = now;
9520                 if (rt === false || t.taskRunCount === t.repeat) {
9521                     removeTask(t);
9522                     return;
9523                 }
9524             }
9525             if (t.duration && t.duration <= (now - t.taskStartTime)) {
9526                 removeTask(t);
9527             }
9528         }
9529     };
9530
9531     /**
9532      * Starts a new task.
9533      * @method start
9534      * @param {Object} task <p>A config object that supports the following properties:<ul>
9535      * <li><code>run</code> : Function<div class="sub-desc"><p>The function to execute each time the task is invoked. The
9536      * function will be called at each interval and passed the <code>args</code> argument if specified, and the
9537      * current invocation count if not.</p>
9538      * <p>If a particular scope (<code>this</code> reference) is required, be sure to specify it using the <code>scope</code> argument.</p>
9539      * <p>Return <code>false</code> from this function to terminate the task.</p></div></li>
9540      * <li><code>interval</code> : Number<div class="sub-desc">The frequency in milliseconds with which the task
9541      * should be invoked.</div></li>
9542      * <li><code>args</code> : Array<div class="sub-desc">(optional) An array of arguments to be passed to the function
9543      * specified by <code>run</code>. If not specified, the current invocation count is passed.</div></li>
9544      * <li><code>scope</code> : Object<div class="sub-desc">(optional) The scope (<tt>this</tt> reference) in which to execute the
9545      * <code>run</code> function. Defaults to the task config object.</div></li>
9546      * <li><code>duration</code> : Number<div class="sub-desc">(optional) The length of time in milliseconds to invoke
9547      * the task before stopping automatically (defaults to indefinite).</div></li>
9548      * <li><code>repeat</code> : Number<div class="sub-desc">(optional) The number of times to invoke the task before
9549      * stopping automatically (defaults to indefinite).</div></li>
9550      * </ul></p>
9551      * <p>Before each invocation, Ext injects the property <code>taskRunCount</code> into the task object so
9552      * that calculations based on the repeat count can be performed.</p>
9553      * @return {Object} The task
9554      */
9555     this.start = function(task) {
9556         tasks.push(task);
9557         task.taskStartTime = new Date().getTime();
9558         task.taskRunTime = 0;
9559         task.taskRunCount = 0;
9560         startThread();
9561         return task;
9562     };
9563
9564     /**
9565      * Stops an existing running task.
9566      * @method stop
9567      * @param {Object} task The task to stop
9568      * @return {Object} The task
9569      */
9570     this.stop = function(task) {
9571         removeTask(task);
9572         return task;
9573     };
9574
9575     /**
9576      * Stops all tasks that are currently running.
9577      * @method stopAll
9578      */
9579     this.stopAll = function() {
9580         stopThread();
9581         for (var i = 0, len = tasks.length; i < len; i++) {
9582             if (tasks[i].onStop) {
9583                 tasks[i].onStop();
9584             }
9585         }
9586         tasks = [];
9587         removeQueue = [];
9588     };
9589 };
9590
9591 /**
9592  * @class Ext.TaskManager
9593  * @extends Ext.util.TaskRunner
9594  * A static {@link Ext.util.TaskRunner} instance that can be used to start and stop arbitrary tasks.  See
9595  * {@link Ext.util.TaskRunner} for supported methods and task config properties.
9596  * <pre><code>
9597 // Start a simple clock task that updates a div once per second
9598 var task = {
9599     run: function(){
9600         Ext.fly('clock').update(new Date().format('g:i:s A'));
9601     },
9602     interval: 1000 //1 second
9603 }
9604 Ext.TaskManager.start(task);
9605 </code></pre>
9606  * <p>See the {@link #start} method for details about how to configure a task object.</p>
9607  * @singleton
9608  */
9609 Ext.TaskManager = Ext.create('Ext.util.TaskRunner');
9610 /**
9611  * @class Ext.is
9612  * 
9613  * Determines information about the current platform the application is running on.
9614  * 
9615  * @singleton
9616  */
9617 Ext.is = {
9618     init : function(navigator) {
9619         var platforms = this.platforms,
9620             ln = platforms.length,
9621             i, platform;
9622
9623         navigator = navigator || window.navigator;
9624
9625         for (i = 0; i < ln; i++) {
9626             platform = platforms[i];
9627             this[platform.identity] = platform.regex.test(navigator[platform.property]);
9628         }
9629
9630         /**
9631          * @property Desktop True if the browser is running on a desktop machine
9632          * @type {Boolean}
9633          */
9634         this.Desktop = this.Mac || this.Windows || (this.Linux && !this.Android);
9635         /**
9636          * @property Tablet True if the browser is running on a tablet (iPad)
9637          */
9638         this.Tablet = this.iPad;
9639         /**
9640          * @property Phone True if the browser is running on a phone.
9641          * @type {Boolean}
9642          */
9643         this.Phone = !this.Desktop && !this.Tablet;
9644         /**
9645          * @property iOS True if the browser is running on iOS
9646          * @type {Boolean}
9647          */
9648         this.iOS = this.iPhone || this.iPad || this.iPod;
9649         
9650         /**
9651          * @property Standalone Detects when application has been saved to homescreen.
9652          * @type {Boolean}
9653          */
9654         this.Standalone = !!window.navigator.standalone;
9655     },
9656     
9657     /**
9658      * @property iPhone True when the browser is running on a iPhone
9659      * @type {Boolean}
9660      */
9661     platforms: [{
9662         property: 'platform',
9663         regex: /iPhone/i,
9664         identity: 'iPhone'
9665     },
9666     
9667     /**
9668      * @property iPod True when the browser is running on a iPod
9669      * @type {Boolean}
9670      */
9671     {
9672         property: 'platform',
9673         regex: /iPod/i,
9674         identity: 'iPod'
9675     },
9676     
9677     /**
9678      * @property iPad True when the browser is running on a iPad
9679      * @type {Boolean}
9680      */
9681     {
9682         property: 'userAgent',
9683         regex: /iPad/i,
9684         identity: 'iPad'
9685     },
9686     
9687     /**
9688      * @property Blackberry True when the browser is running on a Blackberry
9689      * @type {Boolean}
9690      */
9691     {
9692         property: 'userAgent',
9693         regex: /Blackberry/i,
9694         identity: 'Blackberry'
9695     },
9696     
9697     /**
9698      * @property Android True when the browser is running on an Android device
9699      * @type {Boolean}
9700      */
9701     {
9702         property: 'userAgent',
9703         regex: /Android/i,
9704         identity: 'Android'
9705     },
9706     
9707     /**
9708      * @property Mac True when the browser is running on a Mac
9709      * @type {Boolean}
9710      */
9711     {
9712         property: 'platform',
9713         regex: /Mac/i,
9714         identity: 'Mac'
9715     },
9716     
9717     /**
9718      * @property Windows True when the browser is running on Windows
9719      * @type {Boolean}
9720      */
9721     {
9722         property: 'platform',
9723         regex: /Win/i,
9724         identity: 'Windows'
9725     },
9726     
9727     /**
9728      * @property Linux True when the browser is running on Linux
9729      * @type {Boolean}
9730      */
9731     {
9732         property: 'platform',
9733         regex: /Linux/i,
9734         identity: 'Linux'
9735     }]
9736 };
9737
9738 Ext.is.init();
9739
9740 /**
9741  * @class Ext.supports
9742  *
9743  * Determines information about features are supported in the current environment
9744  * 
9745  * @singleton
9746  */
9747 Ext.supports = {
9748     init : function() {
9749         var doc = document,
9750             div = doc.createElement('div'),
9751             tests = this.tests,
9752             ln = tests.length,
9753             i, test;
9754
9755         div.innerHTML = [
9756             '<div style="height:30px;width:50px;">',
9757                 '<div style="height:20px;width:20px;"></div>',
9758             '</div>',
9759             '<div style="width: 200px; height: 200px; position: relative; padding: 5px;">',
9760                 '<div style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"></div>',
9761             '</div>',
9762             '<div style="float:left; background-color:transparent;"></div>'
9763         ].join('');
9764
9765         doc.body.appendChild(div);
9766
9767         for (i = 0; i < ln; i++) {
9768             test = tests[i];
9769             this[test.identity] = test.fn.call(this, doc, div);
9770         }
9771
9772         doc.body.removeChild(div);
9773     },
9774
9775     /**
9776      * @property CSS3BoxShadow True if document environment supports the CSS3 box-shadow style.
9777      * @type {Boolean}
9778      */
9779     CSS3BoxShadow: Ext.isDefined(document.documentElement.style.boxShadow),
9780
9781     /**
9782      * @property ClassList True if document environment supports the HTML5 classList API.
9783      * @type {Boolean}
9784      */
9785     ClassList: !!document.documentElement.classList,
9786
9787     /**
9788      * @property OrientationChange True if the device supports orientation change
9789      * @type {Boolean}
9790      */
9791     OrientationChange: ((typeof window.orientation != 'undefined') && ('onorientationchange' in window)),
9792     
9793     /**
9794      * @property DeviceMotion True if the device supports device motion (acceleration and rotation rate)
9795      * @type {Boolean}
9796      */
9797     DeviceMotion: ('ondevicemotion' in window),
9798     
9799     /**
9800      * @property Touch True if the device supports touch
9801      * @type {Boolean}
9802      */
9803     // is.Desktop is needed due to the bug in Chrome 5.0.375, Safari 3.1.2
9804     // and Safari 4.0 (they all have 'ontouchstart' in the window object).
9805     Touch: ('ontouchstart' in window) && (!Ext.is.Desktop),
9806
9807     tests: [
9808         /**
9809          * @property Transitions True if the device supports CSS3 Transitions
9810          * @type {Boolean}
9811          */
9812         {
9813             identity: 'Transitions',
9814             fn: function(doc, div) {
9815                 var prefix = [
9816                         'webkit',
9817                         'Moz',
9818                         'o',
9819                         'ms',
9820                         'khtml'
9821                     ],
9822                     TE = 'TransitionEnd',
9823                     transitionEndName = [
9824                         prefix[0] + TE,
9825                         'transitionend', //Moz bucks the prefixing convention
9826                         prefix[2] + TE,
9827                         prefix[3] + TE,
9828                         prefix[4] + TE
9829                     ],
9830                     ln = prefix.length,
9831                     i = 0,
9832                     out = false;
9833                 div = Ext.get(div);
9834                 for (; i < ln; i++) {
9835                     if (div.getStyle(prefix[i] + "TransitionProperty")) {
9836                         Ext.supports.CSS3Prefix = prefix[i];
9837                         Ext.supports.CSS3TransitionEnd = transitionEndName[i];
9838                         out = true;
9839                         break;
9840                     }
9841                 }
9842                 return out;
9843             }
9844         },
9845         
9846         /**
9847          * @property RightMargin True if the device supports right margin.
9848          * See https://bugs.webkit.org/show_bug.cgi?id=13343 for why this is needed.
9849          * @type {Boolean}
9850          */
9851         {
9852             identity: 'RightMargin',
9853             fn: function(doc, div, view) {
9854                 view = doc.defaultView;
9855                 return !(view && view.getComputedStyle(div.firstChild.firstChild, null).marginRight != '0px');
9856             }
9857         },
9858         
9859         /**
9860          * @property TransparentColor True if the device supports transparent color
9861          * @type {Boolean}
9862          */
9863         {
9864             identity: 'TransparentColor',
9865             fn: function(doc, div, view) {
9866                 view = doc.defaultView;
9867                 return !(view && view.getComputedStyle(div.lastChild, null).backgroundColor != 'transparent');
9868             }
9869         },
9870
9871         /**
9872          * @property ComputedStyle True if the browser supports document.defaultView.getComputedStyle()
9873          * @type {Boolean}
9874          */
9875         {
9876             identity: 'ComputedStyle',
9877             fn: function(doc, div, view) {
9878                 view = doc.defaultView;
9879                 return view && view.getComputedStyle;
9880             }
9881         },
9882         
9883         /**
9884          * @property SVG True if the device supports SVG
9885          * @type {Boolean}
9886          */
9887         {
9888             identity: 'Svg',
9889             fn: function(doc) {
9890                 return !!doc.createElementNS && !!doc.createElementNS( "http:/" + "/www.w3.org/2000/svg", "svg").createSVGRect;
9891             }
9892         },
9893     
9894         /**
9895          * @property Canvas True if the device supports Canvas
9896          * @type {Boolean}
9897          */
9898         {
9899             identity: 'Canvas',
9900             fn: function(doc) {
9901                 return !!doc.createElement('canvas').getContext;
9902             }
9903         },
9904         
9905         /**
9906          * @property VML True if the device supports VML
9907          * @type {Boolean}
9908          */
9909         {
9910             identity: 'Vml',
9911             fn: function(doc) {
9912                 var d = doc.createElement("div");
9913                 d.innerHTML = "<!--[if vml]><br><br><![endif]-->";
9914                 return (d.childNodes.length == 2);
9915             }
9916         },
9917         
9918         /**
9919          * @property Float True if the device supports CSS float
9920          * @type {Boolean}
9921          */
9922         {
9923             identity: 'Float',
9924             fn: function(doc, div) {
9925                 return !!div.lastChild.style.cssFloat;
9926             }
9927         },
9928         
9929         /**
9930          * @property AudioTag True if the device supports the HTML5 audio tag
9931          * @type {Boolean}
9932          */
9933         {
9934             identity: 'AudioTag',
9935             fn: function(doc) {
9936                 return !!doc.createElement('audio').canPlayType;
9937             }
9938         },
9939         
9940         /**
9941          * @property History True if the device supports HTML5 history
9942          * @type {Boolean}
9943          */
9944         {
9945             identity: 'History',
9946             fn: function() {
9947                 return !!(window.history && history.pushState);
9948             }
9949         },
9950         
9951         /**
9952          * @property CSS3DTransform True if the device supports CSS3DTransform
9953          * @type {Boolean}
9954          */
9955         {
9956             identity: 'CSS3DTransform',
9957             fn: function() {
9958                 return (typeof WebKitCSSMatrix != 'undefined' && new WebKitCSSMatrix().hasOwnProperty('m41'));
9959             }
9960         },
9961
9962                 /**
9963          * @property CSS3LinearGradient True if the device supports CSS3 linear gradients
9964          * @type {Boolean}
9965          */
9966         {
9967             identity: 'CSS3LinearGradient',
9968             fn: function(doc, div) {
9969                 var property = 'background-image:',
9970                     webkit   = '-webkit-gradient(linear, left top, right bottom, from(black), to(white))',
9971                     w3c      = 'linear-gradient(left top, black, white)',
9972                     moz      = '-moz-' + w3c,
9973                     options  = [property + webkit, property + w3c, property + moz];
9974                 
9975                 div.style.cssText = options.join(';');
9976                 
9977                 return ("" + div.style.backgroundImage).indexOf('gradient') !== -1;
9978             }
9979         },
9980         
9981         /**
9982          * @property CSS3BorderRadius True if the device supports CSS3 border radius
9983          * @type {Boolean}
9984          */
9985         {
9986             identity: 'CSS3BorderRadius',
9987             fn: function(doc, div) {
9988                 var domPrefixes = ['borderRadius', 'BorderRadius', 'MozBorderRadius', 'WebkitBorderRadius', 'OBorderRadius', 'KhtmlBorderRadius'],
9989                     pass = false,
9990                     i;
9991                 for (i = 0; i < domPrefixes.length; i++) {
9992                     if (document.body.style[domPrefixes[i]] !== undefined) {
9993                         return true;
9994                     }
9995                 }
9996                 return pass;
9997             }
9998         },
9999         
10000         /**
10001          * @property GeoLocation True if the device supports GeoLocation
10002          * @type {Boolean}
10003          */
10004         {
10005             identity: 'GeoLocation',
10006             fn: function() {
10007                 return (typeof navigator != 'undefined' && typeof navigator.geolocation != 'undefined') || (typeof google != 'undefined' && typeof google.gears != 'undefined');
10008             }
10009         },
10010         /**
10011          * @property MouseEnterLeave True if the browser supports mouseenter and mouseleave events
10012          * @type {Boolean}
10013          */
10014         {
10015             identity: 'MouseEnterLeave',
10016             fn: function(doc, div){
10017                 return ('onmouseenter' in div && 'onmouseleave' in div);
10018             }
10019         },
10020         /**
10021          * @property MouseWheel True if the browser supports the mousewheel event
10022          * @type {Boolean}
10023          */
10024         {
10025             identity: 'MouseWheel',
10026             fn: function(doc, div) {
10027                 return ('onmousewheel' in div);
10028             }
10029         },
10030         /**
10031          * @property Opacity True if the browser supports normal css opacity
10032          * @type {Boolean}
10033          */
10034         {
10035             identity: 'Opacity',
10036             fn: function(doc, div){
10037                 // Not a strict equal comparison in case opacity can be converted to a number.
10038                 if (Ext.isIE6 || Ext.isIE7 || Ext.isIE8) {
10039                     return false;
10040                 }
10041                 div.firstChild.style.cssText = 'opacity:0.73';
10042                 return div.firstChild.style.opacity == '0.73';
10043             }
10044         },
10045         /**
10046          * @property Placeholder True if the browser supports the HTML5 placeholder attribute on inputs
10047          * @type {Boolean}
10048          */
10049         {
10050             identity: 'Placeholder',
10051             fn: function(doc) {
10052                 return 'placeholder' in doc.createElement('input');
10053             }
10054         },
10055         
10056         /**
10057          * @property Direct2DBug True if when asking for an element's dimension via offsetWidth or offsetHeight, 
10058          * getBoundingClientRect, etc. the browser returns the subpixel width rounded to the nearest pixel.
10059          * @type {Boolean}
10060          */
10061         {
10062             identity: 'Direct2DBug',
10063             fn: function() {
10064                 return Ext.isString(document.body.style.msTransformOrigin);
10065             }
10066         },
10067         /**
10068          * @property BoundingClientRect True if the browser supports the getBoundingClientRect method on elements
10069          * @type {Boolean}
10070          */
10071         {
10072             identity: 'BoundingClientRect',
10073             fn: function(doc, div) {
10074                 return Ext.isFunction(div.getBoundingClientRect);
10075             }
10076         },
10077         {
10078             identity: 'IncludePaddingInWidthCalculation',
10079             fn: function(doc, div){
10080                 var el = Ext.get(div.childNodes[1].firstChild);
10081                 return el.getWidth() == 210;
10082             }
10083         },
10084         {
10085             identity: 'IncludePaddingInHeightCalculation',
10086             fn: function(doc, div){
10087                 var el = Ext.get(div.childNodes[1].firstChild);
10088                 return el.getHeight() == 210;
10089             }
10090         },
10091         
10092         /**
10093          * @property ArraySort True if the Array sort native method isn't bugged.
10094          * @type {Boolean}
10095          */
10096         {
10097             identity: 'ArraySort',
10098             fn: function() {
10099                 var a = [1,2,3,4,5].sort(function(){ return 0; });
10100                 return a[0] === 1 && a[1] === 2 && a[2] === 3 && a[3] === 4 && a[4] === 5;
10101             }
10102         },
10103         /**
10104          * @property Range True if browser support document.createRange native method.
10105          * @type {Boolean}
10106          */
10107         {
10108             identity: 'Range',
10109             fn: function() {
10110                 return !!document.createRange;
10111             }
10112         },
10113         /**
10114          * @property CreateContextualFragment True if browser support CreateContextualFragment range native methods.
10115          * @type {Boolean}
10116          */
10117         {
10118             identity: 'CreateContextualFragment',
10119             fn: function() {
10120                 var range = Ext.supports.Range ? document.createRange() : false;
10121                 
10122                 return range && !!range.createContextualFragment;
10123             }
10124         }
10125         
10126     ]
10127 };
10128
10129
10130
10131 /*
10132 Ext JS - JavaScript Library
10133 Copyright (c) 2006-2011, Sencha Inc.
10134 All rights reserved.
10135 licensing@sencha.com
10136 */
10137 /**
10138  * @class Ext.core.DomHelper
10139  * <p>The DomHelper class provides a layer of abstraction from DOM and transparently supports creating
10140  * elements via DOM or using HTML fragments. It also has the ability to create HTML fragment templates
10141  * from your DOM building code.</p>
10142  *
10143  * <p><b><u>DomHelper element specification object</u></b></p>
10144  * <p>A specification object is used when creating elements. Attributes of this object
10145  * are assumed to be element attributes, except for 4 special attributes:
10146  * <div class="mdetail-params"><ul>
10147  * <li><b><tt>tag</tt></b> : <div class="sub-desc">The tag name of the element</div></li>
10148  * <li><b><tt>children</tt></b> : or <tt>cn</tt><div class="sub-desc">An array of the
10149  * same kind of element definition objects to be created and appended. These can be nested
10150  * as deep as you want.</div></li>
10151  * <li><b><tt>cls</tt></b> : <div class="sub-desc">The class attribute of the element.
10152  * This will end up being either the "class" attribute on a HTML fragment or className
10153  * for a DOM node, depending on whether DomHelper is using fragments or DOM.</div></li>
10154  * <li><b><tt>html</tt></b> : <div class="sub-desc">The innerHTML for the element</div></li>
10155  * </ul></div></p>
10156  *
10157  * <p><b><u>Insertion methods</u></b></p>
10158  * <p>Commonly used insertion methods:
10159  * <div class="mdetail-params"><ul>
10160  * <li><b><tt>{@link #append}</tt></b> : <div class="sub-desc"></div></li>
10161  * <li><b><tt>{@link #insertBefore}</tt></b> : <div class="sub-desc"></div></li>
10162  * <li><b><tt>{@link #insertAfter}</tt></b> : <div class="sub-desc"></div></li>
10163  * <li><b><tt>{@link #overwrite}</tt></b> : <div class="sub-desc"></div></li>
10164  * <li><b><tt>{@link #createTemplate}</tt></b> : <div class="sub-desc"></div></li>
10165  * <li><b><tt>{@link #insertHtml}</tt></b> : <div class="sub-desc"></div></li>
10166  * </ul></div></p>
10167  *
10168  * <p><b><u>Example</u></b></p>
10169  * <p>This is an example, where an unordered list with 3 children items is appended to an existing
10170  * element with id <tt>'my-div'</tt>:<br>
10171  <pre><code>
10172 var dh = Ext.core.DomHelper; // create shorthand alias
10173 // specification object
10174 var spec = {
10175     id: 'my-ul',
10176     tag: 'ul',
10177     cls: 'my-list',
10178     // append children after creating
10179     children: [     // may also specify 'cn' instead of 'children'
10180         {tag: 'li', id: 'item0', html: 'List Item 0'},
10181         {tag: 'li', id: 'item1', html: 'List Item 1'},
10182         {tag: 'li', id: 'item2', html: 'List Item 2'}
10183     ]
10184 };
10185 var list = dh.append(
10186     'my-div', // the context element 'my-div' can either be the id or the actual node
10187     spec      // the specification object
10188 );
10189  </code></pre></p>
10190  * <p>Element creation specification parameters in this class may also be passed as an Array of
10191  * specification objects. This can be used to insert multiple sibling nodes into an existing
10192  * container very efficiently. For example, to add more list items to the example above:<pre><code>
10193 dh.append('my-ul', [
10194     {tag: 'li', id: 'item3', html: 'List Item 3'},
10195     {tag: 'li', id: 'item4', html: 'List Item 4'}
10196 ]);
10197  * </code></pre></p>
10198  *
10199  * <p><b><u>Templating</u></b></p>
10200  * <p>The real power is in the built-in templating. Instead of creating or appending any elements,
10201  * <tt>{@link #createTemplate}</tt> returns a Template object which can be used over and over to
10202  * insert new elements. Revisiting the example above, we could utilize templating this time:
10203  * <pre><code>
10204 // create the node
10205 var list = dh.append('my-div', {tag: 'ul', cls: 'my-list'});
10206 // get template
10207 var tpl = dh.createTemplate({tag: 'li', id: 'item{0}', html: 'List Item {0}'});
10208
10209 for(var i = 0; i < 5, i++){
10210     tpl.append(list, [i]); // use template to append to the actual node
10211 }
10212  * </code></pre></p>
10213  * <p>An example using a template:<pre><code>
10214 var html = '<a id="{0}" href="{1}" class="nav">{2}</a>';
10215
10216 var tpl = new Ext.core.DomHelper.createTemplate(html);
10217 tpl.append('blog-roll', ['link1', 'http://www.edspencer.net/', "Ed&#39;s Site"]);
10218 tpl.append('blog-roll', ['link2', 'http://www.dustindiaz.com/', "Dustin&#39;s Site"]);
10219  * </code></pre></p>
10220  *
10221  * <p>The same example using named parameters:<pre><code>
10222 var html = '<a id="{id}" href="{url}" class="nav">{text}</a>';
10223
10224 var tpl = new Ext.core.DomHelper.createTemplate(html);
10225 tpl.append('blog-roll', {
10226     id: 'link1',
10227     url: 'http://www.edspencer.net/',
10228     text: "Ed&#39;s Site"
10229 });
10230 tpl.append('blog-roll', {
10231     id: 'link2',
10232     url: 'http://www.dustindiaz.com/',
10233     text: "Dustin&#39;s Site"
10234 });
10235  * </code></pre></p>
10236  *
10237  * <p><b><u>Compiling Templates</u></b></p>
10238  * <p>Templates are applied using regular expressions. The performance is great, but if
10239  * you are adding a bunch of DOM elements using the same template, you can increase
10240  * performance even further by {@link Ext.Template#compile "compiling"} the template.
10241  * The way "{@link Ext.Template#compile compile()}" works is the template is parsed and
10242  * broken up at the different variable points and a dynamic function is created and eval'ed.
10243  * The generated function performs string concatenation of these parts and the passed
10244  * variables instead of using regular expressions.
10245  * <pre><code>
10246 var html = '<a id="{id}" href="{url}" class="nav">{text}</a>';
10247
10248 var tpl = new Ext.core.DomHelper.createTemplate(html);
10249 tpl.compile();
10250
10251 //... use template like normal
10252  * </code></pre></p>
10253  *
10254  * <p><b><u>Performance Boost</u></b></p>
10255  * <p>DomHelper will transparently create HTML fragments when it can. Using HTML fragments instead
10256  * of DOM can significantly boost performance.</p>
10257  * <p>Element creation specification parameters may also be strings. If {@link #useDom} is <tt>false</tt>,
10258  * then the string is used as innerHTML. If {@link #useDom} is <tt>true</tt>, a string specification
10259  * results in the creation of a text node. Usage:</p>
10260  * <pre><code>
10261 Ext.core.DomHelper.useDom = true; // force it to use DOM; reduces performance
10262  * </code></pre>
10263  * @singleton
10264  */
10265 Ext.ns('Ext.core');
10266 Ext.core.DomHelper = function(){
10267     var tempTableEl = null,
10268         emptyTags = /^(?:br|frame|hr|img|input|link|meta|range|spacer|wbr|area|param|col)$/i,
10269         tableRe = /^table|tbody|tr|td$/i,
10270         confRe = /tag|children|cn|html$/i,
10271         tableElRe = /td|tr|tbody/i,
10272         endRe = /end/i,
10273         pub,
10274         // kill repeat to save bytes
10275         afterbegin = 'afterbegin',
10276         afterend = 'afterend',
10277         beforebegin = 'beforebegin',
10278         beforeend = 'beforeend',
10279         ts = '<table>',
10280         te = '</table>',
10281         tbs = ts+'<tbody>',
10282         tbe = '</tbody>'+te,
10283         trs = tbs + '<tr>',
10284         tre = '</tr>'+tbe;
10285
10286     // private
10287     function doInsert(el, o, returnElement, pos, sibling, append){
10288         el = Ext.getDom(el);
10289         var newNode;
10290         if (pub.useDom) {
10291             newNode = createDom(o, null);
10292             if (append) {
10293                 el.appendChild(newNode);
10294             } else {
10295                 (sibling == 'firstChild' ? el : el.parentNode).insertBefore(newNode, el[sibling] || el);
10296             }
10297         } else {
10298             newNode = Ext.core.DomHelper.insertHtml(pos, el, Ext.core.DomHelper.createHtml(o));
10299         }
10300         return returnElement ? Ext.get(newNode, true) : newNode;
10301     }
10302     
10303     function createDom(o, parentNode){
10304         var el,
10305             doc = document,
10306             useSet,
10307             attr,
10308             val,
10309             cn;
10310
10311         if (Ext.isArray(o)) {                       // Allow Arrays of siblings to be inserted
10312             el = doc.createDocumentFragment(); // in one shot using a DocumentFragment
10313             for (var i = 0, l = o.length; i < l; i++) {
10314                 createDom(o[i], el);
10315             }
10316         } else if (typeof o == 'string') {         // Allow a string as a child spec.
10317             el = doc.createTextNode(o);
10318         } else {
10319             el = doc.createElement( o.tag || 'div' );
10320             useSet = !!el.setAttribute; // In IE some elements don't have setAttribute
10321             for (attr in o) {
10322                 if(!confRe.test(attr)){
10323                     val = o[attr];
10324                     if(attr == 'cls'){
10325                         el.className = val;
10326                     }else{
10327                         if(useSet){
10328                             el.setAttribute(attr, val);
10329                         }else{
10330                             el[attr] = val;
10331                         }
10332                     }
10333                 }
10334             }
10335             Ext.core.DomHelper.applyStyles(el, o.style);
10336
10337             if ((cn = o.children || o.cn)) {
10338                 createDom(cn, el);
10339             } else if (o.html) {
10340                 el.innerHTML = o.html;
10341             }
10342         }
10343         if(parentNode){
10344            parentNode.appendChild(el);
10345         }
10346         return el;
10347     }
10348
10349     // build as innerHTML where available
10350     function createHtml(o){
10351         var b = '',
10352             attr,
10353             val,
10354             key,
10355             cn,
10356             i;
10357
10358         if(typeof o == "string"){
10359             b = o;
10360         } else if (Ext.isArray(o)) {
10361             for (i=0; i < o.length; i++) {
10362                 if(o[i]) {
10363                     b += createHtml(o[i]);
10364                 }
10365             }
10366         } else {
10367             b += '<' + (o.tag = o.tag || 'div');
10368             for (attr in o) {
10369                 val = o[attr];
10370                 if(!confRe.test(attr)){
10371                     if (typeof val == "object") {
10372                         b += ' ' + attr + '="';
10373                         for (key in val) {
10374                             b += key + ':' + val[key] + ';';
10375                         }
10376                         b += '"';
10377                     }else{
10378                         b += ' ' + ({cls : 'class', htmlFor : 'for'}[attr] || attr) + '="' + val + '"';
10379                     }
10380                 }
10381             }
10382             // Now either just close the tag or try to add children and close the tag.
10383             if (emptyTags.test(o.tag)) {
10384                 b += '/>';
10385             } else {
10386                 b += '>';
10387                 if ((cn = o.children || o.cn)) {
10388                     b += createHtml(cn);
10389                 } else if(o.html){
10390                     b += o.html;
10391                 }
10392                 b += '</' + o.tag + '>';
10393             }
10394         }
10395         return b;
10396     }
10397
10398     function ieTable(depth, s, h, e){
10399         tempTableEl.innerHTML = [s, h, e].join('');
10400         var i = -1,
10401             el = tempTableEl,
10402             ns;
10403         while(++i < depth){
10404             el = el.firstChild;
10405         }
10406 //      If the result is multiple siblings, then encapsulate them into one fragment.
10407         ns = el.nextSibling;
10408         if (ns){
10409             var df = document.createDocumentFragment();
10410             while(el){
10411                 ns = el.nextSibling;
10412                 df.appendChild(el);
10413                 el = ns;
10414             }
10415             el = df;
10416         }
10417         return el;
10418     }
10419
10420     /**
10421      * @ignore
10422      * Nasty code for IE's broken table implementation
10423      */
10424     function insertIntoTable(tag, where, el, html) {
10425         var node,
10426             before;
10427
10428         tempTableEl = tempTableEl || document.createElement('div');
10429
10430         if(tag == 'td' && (where == afterbegin || where == beforeend) ||
10431            !tableElRe.test(tag) && (where == beforebegin || where == afterend)) {
10432             return null;
10433         }
10434         before = where == beforebegin ? el :
10435                  where == afterend ? el.nextSibling :
10436                  where == afterbegin ? el.firstChild : null;
10437
10438         if (where == beforebegin || where == afterend) {
10439             el = el.parentNode;
10440         }
10441
10442         if (tag == 'td' || (tag == 'tr' && (where == beforeend || where == afterbegin))) {
10443             node = ieTable(4, trs, html, tre);
10444         } else if ((tag == 'tbody' && (where == beforeend || where == afterbegin)) ||
10445                    (tag == 'tr' && (where == beforebegin || where == afterend))) {
10446             node = ieTable(3, tbs, html, tbe);
10447         } else {
10448             node = ieTable(2, ts, html, te);
10449         }
10450         el.insertBefore(node, before);
10451         return node;
10452     }
10453     
10454     /**
10455      * @ignore
10456      * Fix for IE9 createContextualFragment missing method
10457      */   
10458     function createContextualFragment(html){
10459         var div = document.createElement("div"),
10460             fragment = document.createDocumentFragment(),
10461             i = 0,
10462             length, childNodes;
10463         
10464         div.innerHTML = html;
10465         childNodes = div.childNodes;
10466         length = childNodes.length;
10467
10468         for (; i < length; i++) {
10469             fragment.appendChild(childNodes[i].cloneNode(true));
10470         }
10471
10472         return fragment;
10473     }
10474     
10475     pub = {
10476         /**
10477          * Returns the markup for the passed Element(s) config.
10478          * @param {Object} o The DOM object spec (and children)
10479          * @return {String}
10480          */
10481         markup : function(o){
10482             return createHtml(o);
10483         },
10484
10485         /**
10486          * Applies a style specification to an element.
10487          * @param {String/HTMLElement} el The element to apply styles to
10488          * @param {String/Object/Function} styles A style specification string e.g. 'width:100px', or object in the form {width:'100px'}, or
10489          * a function which returns such a specification.
10490          */
10491         applyStyles : function(el, styles){
10492             if (styles) {
10493                 el = Ext.fly(el);
10494                 if (typeof styles == "function") {
10495                     styles = styles.call();
10496                 }
10497                 if (typeof styles == "string") {
10498                     styles = Ext.core.Element.parseStyles(styles);
10499                 }
10500                 if (typeof styles == "object") {
10501                     el.setStyle(styles);
10502                 }
10503             }
10504         },
10505
10506         /**
10507          * Inserts an HTML fragment into the DOM.
10508          * @param {String} where Where to insert the html in relation to el - beforeBegin, afterBegin, beforeEnd, afterEnd.
10509          * @param {HTMLElement/TextNode} el The context element
10510          * @param {String} html The HTML fragment
10511          * @return {HTMLElement} The new node
10512          */
10513         insertHtml : function(where, el, html){
10514             var hash = {},
10515                 hashVal,
10516                 range,
10517                 rangeEl,
10518                 setStart,
10519                 frag,
10520                 rs;
10521
10522             where = where.toLowerCase();
10523             // add these here because they are used in both branches of the condition.
10524             hash[beforebegin] = ['BeforeBegin', 'previousSibling'];
10525             hash[afterend] = ['AfterEnd', 'nextSibling'];
10526             
10527             // if IE and context element is an HTMLElement
10528             if (el.insertAdjacentHTML) {
10529                 if(tableRe.test(el.tagName) && (rs = insertIntoTable(el.tagName.toLowerCase(), where, el, html))){
10530                     return rs;
10531                 }
10532                 
10533                 // add these two to the hash.
10534                 hash[afterbegin] = ['AfterBegin', 'firstChild'];
10535                 hash[beforeend] = ['BeforeEnd', 'lastChild'];
10536                 if ((hashVal = hash[where])) {
10537                     el.insertAdjacentHTML(hashVal[0], html);
10538                     return el[hashVal[1]];
10539                 }
10540             // if (not IE and context element is an HTMLElement) or TextNode
10541             } else {
10542                 // we cannot insert anything inside a textnode so...
10543                 if (Ext.isTextNode(el)) {
10544                     where = where === 'afterbegin' ? 'beforebegin' : where; 
10545                     where = where === 'beforeend' ? 'afterend' : where;
10546                 }
10547                 range = Ext.supports.CreateContextualFragment ? el.ownerDocument.createRange() : undefined;
10548                 setStart = 'setStart' + (endRe.test(where) ? 'After' : 'Before');
10549                 if (hash[where]) {
10550                     if (range) {
10551                         range[setStart](el);
10552                         frag = range.createContextualFragment(html);
10553                     } else {
10554                         frag = createContextualFragment(html);
10555                     }
10556                     el.parentNode.insertBefore(frag, where == beforebegin ? el : el.nextSibling);
10557                     return el[(where == beforebegin ? 'previous' : 'next') + 'Sibling'];
10558                 } else {
10559                     rangeEl = (where == afterbegin ? 'first' : 'last') + 'Child';
10560                     if (el.firstChild) {
10561                         if (range) {
10562                             range[setStart](el[rangeEl]);
10563                             frag = range.createContextualFragment(html);
10564                         } else {
10565                             frag = createContextualFragment(html);
10566                         }
10567                         
10568                         if(where == afterbegin){
10569                             el.insertBefore(frag, el.firstChild);
10570                         }else{
10571                             el.appendChild(frag);
10572                         }
10573                     } else {
10574                         el.innerHTML = html;
10575                     }
10576                     return el[rangeEl];
10577                 }
10578             }
10579             Ext.Error.raise({
10580                 sourceClass: 'Ext.core.DomHelper',
10581                 sourceMethod: 'insertHtml',
10582                 htmlToInsert: html,
10583                 targetElement: el,
10584                 msg: 'Illegal insertion point reached: "' + where + '"'
10585             });
10586         },
10587
10588         /**
10589          * Creates new DOM element(s) and inserts them before el.
10590          * @param {Mixed} el The context element
10591          * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
10592          * @param {Boolean} returnElement (optional) true to return a Ext.core.Element
10593          * @return {HTMLElement/Ext.core.Element} The new node
10594          */
10595         insertBefore : function(el, o, returnElement){
10596             return doInsert(el, o, returnElement, beforebegin);
10597         },
10598
10599         /**
10600          * Creates new DOM element(s) and inserts them after el.
10601          * @param {Mixed} el The context element
10602          * @param {Object} o The DOM object spec (and children)
10603          * @param {Boolean} returnElement (optional) true to return a Ext.core.Element
10604          * @return {HTMLElement/Ext.core.Element} The new node
10605          */
10606         insertAfter : function(el, o, returnElement){
10607             return doInsert(el, o, returnElement, afterend, 'nextSibling');
10608         },
10609
10610         /**
10611          * Creates new DOM element(s) and inserts them as the first child of el.
10612          * @param {Mixed} el The context element
10613          * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
10614          * @param {Boolean} returnElement (optional) true to return a Ext.core.Element
10615          * @return {HTMLElement/Ext.core.Element} The new node
10616          */
10617         insertFirst : function(el, o, returnElement){
10618             return doInsert(el, o, returnElement, afterbegin, 'firstChild');
10619         },
10620
10621         /**
10622          * Creates new DOM element(s) and appends them to el.
10623          * @param {Mixed} el The context element
10624          * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
10625          * @param {Boolean} returnElement (optional) true to return a Ext.core.Element
10626          * @return {HTMLElement/Ext.core.Element} The new node
10627          */
10628         append : function(el, o, returnElement){
10629             return doInsert(el, o, returnElement, beforeend, '', true);
10630         },
10631
10632         /**
10633          * Creates new DOM element(s) and overwrites the contents of el with them.
10634          * @param {Mixed} el The context element
10635          * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
10636          * @param {Boolean} returnElement (optional) true to return a Ext.core.Element
10637          * @return {HTMLElement/Ext.core.Element} The new node
10638          */
10639         overwrite : function(el, o, returnElement){
10640             el = Ext.getDom(el);
10641             el.innerHTML = createHtml(o);
10642             return returnElement ? Ext.get(el.firstChild) : el.firstChild;
10643         },
10644
10645         createHtml : createHtml,
10646         
10647         /**
10648          * Creates new DOM element(s) without inserting them to the document.
10649          * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
10650          * @return {HTMLElement} The new uninserted node
10651          */
10652         createDom: createDom,
10653         
10654         /** True to force the use of DOM instead of html fragments @type Boolean */
10655         useDom : false,
10656         
10657         /**
10658          * Creates a new Ext.Template from the DOM object spec.
10659          * @param {Object} o The DOM object spec (and children)
10660          * @return {Ext.Template} The new template
10661          */
10662         createTemplate : function(o){
10663             var html = Ext.core.DomHelper.createHtml(o);
10664             return Ext.create('Ext.Template', html);
10665         }
10666     };
10667     return pub;
10668 }();
10669
10670 /*
10671  * This is code is also distributed under MIT license for use
10672  * with jQuery and prototype JavaScript libraries.
10673  */
10674 /**
10675  * @class Ext.DomQuery
10676 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).
10677 <p>
10678 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>
10679
10680 <p>
10681 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.
10682 </p>
10683 <h4>Element Selectors:</h4>
10684 <ul class="list">
10685     <li> <b>*</b> any element</li>
10686     <li> <b>E</b> an element with the tag E</li>
10687     <li> <b>E F</b> All descendent elements of E that have the tag F</li>
10688     <li> <b>E > F</b> or <b>E/F</b> all direct children elements of E that have the tag F</li>
10689     <li> <b>E + F</b> all elements with the tag F that are immediately preceded by an element with the tag E</li>
10690     <li> <b>E ~ F</b> all elements with the tag F that are preceded by a sibling element with the tag E</li>
10691 </ul>
10692 <h4>Attribute Selectors:</h4>
10693 <p>The use of &#64; and quotes are optional. For example, div[&#64;foo='bar'] is also a valid attribute selector.</p>
10694 <ul class="list">
10695     <li> <b>E[foo]</b> has an attribute "foo"</li>
10696     <li> <b>E[foo=bar]</b> has an attribute "foo" that equals "bar"</li>
10697     <li> <b>E[foo^=bar]</b> has an attribute "foo" that starts with "bar"</li>
10698     <li> <b>E[foo$=bar]</b> has an attribute "foo" that ends with "bar"</li>
10699     <li> <b>E[foo*=bar]</b> has an attribute "foo" that contains the substring "bar"</li>
10700     <li> <b>E[foo%=2]</b> has an attribute "foo" that is evenly divisible by 2</li>
10701     <li> <b>E[foo!=bar]</b> attribute "foo" does not equal "bar"</li>
10702 </ul>
10703 <h4>Pseudo Classes:</h4>
10704 <ul class="list">
10705     <li> <b>E:first-child</b> E is the first child of its parent</li>
10706     <li> <b>E:last-child</b> E is the last child of its parent</li>
10707     <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>
10708     <li> <b>E:nth-child(odd)</b> E is an odd child of its parent</li>
10709     <li> <b>E:nth-child(even)</b> E is an even child of its parent</li>
10710     <li> <b>E:only-child</b> E is the only child of its parent</li>
10711     <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>
10712     <li> <b>E:first</b> the first E in the resultset</li>
10713     <li> <b>E:last</b> the last E in the resultset</li>
10714     <li> <b>E:nth(<i>n</i>)</b> the <i>n</i>th E in the resultset (1 based)</li>
10715     <li> <b>E:odd</b> shortcut for :nth-child(odd)</li>
10716     <li> <b>E:even</b> shortcut for :nth-child(even)</li>
10717     <li> <b>E:contains(foo)</b> E's innerHTML contains the substring "foo"</li>
10718     <li> <b>E:nodeValue(foo)</b> E contains a textNode with a nodeValue that equals "foo"</li>
10719     <li> <b>E:not(S)</b> an E element that does not match simple selector S</li>
10720     <li> <b>E:has(S)</b> an E element that has a descendent that matches simple selector S</li>
10721     <li> <b>E:next(S)</b> an E element whose next sibling matches simple selector S</li>
10722     <li> <b>E:prev(S)</b> an E element whose previous sibling matches simple selector S</li>
10723     <li> <b>E:any(S1|S2|S2)</b> an E element which matches any of the simple selectors S1, S2 or S3//\\</li>
10724 </ul>
10725 <h4>CSS Value Selectors:</h4>
10726 <ul class="list">
10727     <li> <b>E{display=none}</b> css value "display" that equals "none"</li>
10728     <li> <b>E{display^=none}</b> css value "display" that starts with "none"</li>
10729     <li> <b>E{display$=none}</b> css value "display" that ends with "none"</li>
10730     <li> <b>E{display*=none}</b> css value "display" that contains the substring "none"</li>
10731     <li> <b>E{display%=2}</b> css value "display" that is evenly divisible by 2</li>
10732     <li> <b>E{display!=none}</b> css value "display" that does not equal "none"</li>
10733 </ul>
10734  * @singleton
10735  */
10736 Ext.ns('Ext.core');
10737
10738 Ext.core.DomQuery = Ext.DomQuery = function(){
10739     var cache = {},
10740         simpleCache = {},
10741         valueCache = {},
10742         nonSpace = /\S/,
10743         trimRe = /^\s+|\s+$/g,
10744         tplRe = /\{(\d+)\}/g,
10745         modeRe = /^(\s?[\/>+~]\s?|\s|$)/,
10746         tagTokenRe = /^(#)?([\w-\*]+)/,
10747         nthRe = /(\d*)n\+?(\d*)/,
10748         nthRe2 = /\D/,
10749         // This is for IE MSXML which does not support expandos.
10750     // IE runs the same speed using setAttribute, however FF slows way down
10751     // and Safari completely fails so they need to continue to use expandos.
10752     isIE = window.ActiveXObject ? true : false,
10753     key = 30803;
10754
10755     // this eval is stop the compressor from
10756     // renaming the variable to something shorter
10757     eval("var batch = 30803;");
10758
10759     // Retrieve the child node from a particular
10760     // parent at the specified index.
10761     function child(parent, index){
10762         var i = 0,
10763             n = parent.firstChild;
10764         while(n){
10765             if(n.nodeType == 1){
10766                if(++i == index){
10767                    return n;
10768                }
10769             }
10770             n = n.nextSibling;
10771         }
10772         return null;
10773     }
10774
10775     // retrieve the next element node
10776     function next(n){
10777         while((n = n.nextSibling) && n.nodeType != 1);
10778         return n;
10779     }
10780
10781     // retrieve the previous element node
10782     function prev(n){
10783         while((n = n.previousSibling) && n.nodeType != 1);
10784         return n;
10785     }
10786
10787     // Mark each child node with a nodeIndex skipping and
10788     // removing empty text nodes.
10789     function children(parent){
10790         var n = parent.firstChild,
10791         nodeIndex = -1,
10792         nextNode;
10793         while(n){
10794             nextNode = n.nextSibling;
10795             // clean worthless empty nodes.
10796             if(n.nodeType == 3 && !nonSpace.test(n.nodeValue)){
10797             parent.removeChild(n);
10798             }else{
10799             // add an expando nodeIndex
10800             n.nodeIndex = ++nodeIndex;
10801             }
10802             n = nextNode;
10803         }
10804         return this;
10805     }
10806
10807
10808     // nodeSet - array of nodes
10809     // cls - CSS Class
10810     function byClassName(nodeSet, cls){
10811         if(!cls){
10812             return nodeSet;
10813         }
10814         var result = [], ri = -1;
10815         for(var i = 0, ci; ci = nodeSet[i]; i++){
10816             if((' '+ci.className+' ').indexOf(cls) != -1){
10817                 result[++ri] = ci;
10818             }
10819         }
10820         return result;
10821     };
10822
10823     function attrValue(n, attr){
10824         // if its an array, use the first node.
10825         if(!n.tagName && typeof n.length != "undefined"){
10826             n = n[0];
10827         }
10828         if(!n){
10829             return null;
10830         }
10831
10832         if(attr == "for"){
10833             return n.htmlFor;
10834         }
10835         if(attr == "class" || attr == "className"){
10836             return n.className;
10837         }
10838         return n.getAttribute(attr) || n[attr];
10839
10840     };
10841
10842
10843     // ns - nodes
10844     // mode - false, /, >, +, ~
10845     // tagName - defaults to "*"
10846     function getNodes(ns, mode, tagName){
10847         var result = [], ri = -1, cs;
10848         if(!ns){
10849             return result;
10850         }
10851         tagName = tagName || "*";
10852         // convert to array
10853         if(typeof ns.getElementsByTagName != "undefined"){
10854             ns = [ns];
10855         }
10856
10857         // no mode specified, grab all elements by tagName
10858         // at any depth
10859         if(!mode){
10860             for(var i = 0, ni; ni = ns[i]; i++){
10861                 cs = ni.getElementsByTagName(tagName);
10862                 for(var j = 0, ci; ci = cs[j]; j++){
10863                     result[++ri] = ci;
10864                 }
10865             }
10866         // Direct Child mode (/ or >)
10867         // E > F or E/F all direct children elements of E that have the tag
10868         } else if(mode == "/" || mode == ">"){
10869             var utag = tagName.toUpperCase();
10870             for(var i = 0, ni, cn; ni = ns[i]; i++){
10871                 cn = ni.childNodes;
10872                 for(var j = 0, cj; cj = cn[j]; j++){
10873                     if(cj.nodeName == utag || cj.nodeName == tagName  || tagName == '*'){
10874                         result[++ri] = cj;
10875                     }
10876                 }
10877             }
10878         // Immediately Preceding mode (+)
10879         // E + F all elements with the tag F that are immediately preceded by an element with the tag E
10880         }else if(mode == "+"){
10881             var utag = tagName.toUpperCase();
10882             for(var i = 0, n; n = ns[i]; i++){
10883                 while((n = n.nextSibling) && n.nodeType != 1);
10884                 if(n && (n.nodeName == utag || n.nodeName == tagName || tagName == '*')){
10885                     result[++ri] = n;
10886                 }
10887             }
10888         // Sibling mode (~)
10889         // E ~ F all elements with the tag F that are preceded by a sibling element with the tag E
10890         }else if(mode == "~"){
10891             var utag = tagName.toUpperCase();
10892             for(var i = 0, n; n = ns[i]; i++){
10893                 while((n = n.nextSibling)){
10894                     if (n.nodeName == utag || n.nodeName == tagName || tagName == '*'){
10895                         result[++ri] = n;
10896                     }
10897                 }
10898             }
10899         }
10900         return result;
10901     }
10902
10903     function concat(a, b){
10904         if(b.slice){
10905             return a.concat(b);
10906         }
10907         for(var i = 0, l = b.length; i < l; i++){
10908             a[a.length] = b[i];
10909         }
10910         return a;
10911     }
10912
10913     function byTag(cs, tagName){
10914         if(cs.tagName || cs == document){
10915             cs = [cs];
10916         }
10917         if(!tagName){
10918             return cs;
10919         }
10920         var result = [], ri = -1;
10921         tagName = tagName.toLowerCase();
10922         for(var i = 0, ci; ci = cs[i]; i++){
10923             if(ci.nodeType == 1 && ci.tagName.toLowerCase() == tagName){
10924                 result[++ri] = ci;
10925             }
10926         }
10927         return result;
10928     }
10929
10930     function byId(cs, id){
10931         if(cs.tagName || cs == document){
10932             cs = [cs];
10933         }
10934         if(!id){
10935             return cs;
10936         }
10937         var result = [], ri = -1;
10938         for(var i = 0, ci; ci = cs[i]; i++){
10939             if(ci && ci.id == id){
10940                 result[++ri] = ci;
10941                 return result;
10942             }
10943         }
10944         return result;
10945     }
10946
10947     // operators are =, !=, ^=, $=, *=, %=, |= and ~=
10948     // custom can be "{"
10949     function byAttribute(cs, attr, value, op, custom){
10950         var result = [],
10951             ri = -1,
10952             useGetStyle = custom == "{",
10953             fn = Ext.DomQuery.operators[op],
10954             a,
10955             xml,
10956             hasXml;
10957
10958         for(var i = 0, ci; ci = cs[i]; i++){
10959             // skip non-element nodes.
10960             if(ci.nodeType != 1){
10961                 continue;
10962             }
10963             // only need to do this for the first node
10964             if(!hasXml){
10965                 xml = Ext.DomQuery.isXml(ci);
10966                 hasXml = true;
10967             }
10968
10969             // we only need to change the property names if we're dealing with html nodes, not XML
10970             if(!xml){
10971                 if(useGetStyle){
10972                     a = Ext.DomQuery.getStyle(ci, attr);
10973                 } else if (attr == "class" || attr == "className"){
10974                     a = ci.className;
10975                 } else if (attr == "for"){
10976                     a = ci.htmlFor;
10977                 } else if (attr == "href"){
10978                     // getAttribute href bug
10979                     // http://www.glennjones.net/Post/809/getAttributehrefbug.htm
10980                     a = ci.getAttribute("href", 2);
10981                 } else{
10982                     a = ci.getAttribute(attr);
10983                 }
10984             }else{
10985                 a = ci.getAttribute(attr);
10986             }
10987             if((fn && fn(a, value)) || (!fn && a)){
10988                 result[++ri] = ci;
10989             }
10990         }
10991         return result;
10992     }
10993
10994     function byPseudo(cs, name, value){
10995         return Ext.DomQuery.pseudos[name](cs, value);
10996     }
10997
10998     function nodupIEXml(cs){
10999         var d = ++key,
11000             r;
11001         cs[0].setAttribute("_nodup", d);
11002         r = [cs[0]];
11003         for(var i = 1, len = cs.length; i < len; i++){
11004             var c = cs[i];
11005             if(!c.getAttribute("_nodup") != d){
11006                 c.setAttribute("_nodup", d);
11007                 r[r.length] = c;
11008             }
11009         }
11010         for(var i = 0, len = cs.length; i < len; i++){
11011             cs[i].removeAttribute("_nodup");
11012         }
11013         return r;
11014     }
11015
11016     function nodup(cs){
11017         if(!cs){
11018             return [];
11019         }
11020         var len = cs.length, c, i, r = cs, cj, ri = -1;
11021         if(!len || typeof cs.nodeType != "undefined" || len == 1){
11022             return cs;
11023         }
11024         if(isIE && typeof cs[0].selectSingleNode != "undefined"){
11025             return nodupIEXml(cs);
11026         }
11027         var d = ++key;
11028         cs[0]._nodup = d;
11029         for(i = 1; c = cs[i]; i++){
11030             if(c._nodup != d){
11031                 c._nodup = d;
11032             }else{
11033                 r = [];
11034                 for(var j = 0; j < i; j++){
11035                     r[++ri] = cs[j];
11036                 }
11037                 for(j = i+1; cj = cs[j]; j++){
11038                     if(cj._nodup != d){
11039                         cj._nodup = d;
11040                         r[++ri] = cj;
11041                     }
11042                 }
11043                 return r;
11044             }
11045         }
11046         return r;
11047     }
11048
11049     function quickDiffIEXml(c1, c2){
11050         var d = ++key,
11051             r = [];
11052         for(var i = 0, len = c1.length; i < len; i++){
11053             c1[i].setAttribute("_qdiff", d);
11054         }
11055         for(var i = 0, len = c2.length; i < len; i++){
11056             if(c2[i].getAttribute("_qdiff") != d){
11057                 r[r.length] = c2[i];
11058             }
11059         }
11060         for(var i = 0, len = c1.length; i < len; i++){
11061            c1[i].removeAttribute("_qdiff");
11062         }
11063         return r;
11064     }
11065
11066     function quickDiff(c1, c2){
11067         var len1 = c1.length,
11068             d = ++key,
11069             r = [];
11070         if(!len1){
11071             return c2;
11072         }
11073         if(isIE && typeof c1[0].selectSingleNode != "undefined"){
11074             return quickDiffIEXml(c1, c2);
11075         }
11076         for(var i = 0; i < len1; i++){
11077             c1[i]._qdiff = d;
11078         }
11079         for(var i = 0, len = c2.length; i < len; i++){
11080             if(c2[i]._qdiff != d){
11081                 r[r.length] = c2[i];
11082             }
11083         }
11084         return r;
11085     }
11086
11087     function quickId(ns, mode, root, id){
11088         if(ns == root){
11089            var d = root.ownerDocument || root;
11090            return d.getElementById(id);
11091         }
11092         ns = getNodes(ns, mode, "*");
11093         return byId(ns, id);
11094     }
11095
11096     return {
11097         getStyle : function(el, name){
11098             return Ext.fly(el).getStyle(name);
11099         },
11100         /**
11101          * Compiles a selector/xpath query into a reusable function. The returned function
11102          * takes one parameter "root" (optional), which is the context node from where the query should start.
11103          * @param {String} selector The selector/xpath query
11104          * @param {String} type (optional) Either "select" (the default) or "simple" for a simple selector match
11105          * @return {Function}
11106          */
11107         compile : function(path, type){
11108             type = type || "select";
11109
11110             // setup fn preamble
11111             var fn = ["var f = function(root){\n var mode; ++batch; var n = root || document;\n"],
11112                 mode,
11113                 lastPath,
11114                 matchers = Ext.DomQuery.matchers,
11115                 matchersLn = matchers.length,
11116                 modeMatch,
11117                 // accept leading mode switch
11118                 lmode = path.match(modeRe);
11119
11120             if(lmode && lmode[1]){
11121                 fn[fn.length] = 'mode="'+lmode[1].replace(trimRe, "")+'";';
11122                 path = path.replace(lmode[1], "");
11123             }
11124
11125             // strip leading slashes
11126             while(path.substr(0, 1)=="/"){
11127                 path = path.substr(1);
11128             }
11129
11130             while(path && lastPath != path){
11131                 lastPath = path;
11132                 var tokenMatch = path.match(tagTokenRe);
11133                 if(type == "select"){
11134                     if(tokenMatch){
11135                         // ID Selector
11136                         if(tokenMatch[1] == "#"){
11137                             fn[fn.length] = 'n = quickId(n, mode, root, "'+tokenMatch[2]+'");';
11138                         }else{
11139                             fn[fn.length] = 'n = getNodes(n, mode, "'+tokenMatch[2]+'");';
11140                         }
11141                         path = path.replace(tokenMatch[0], "");
11142                     }else if(path.substr(0, 1) != '@'){
11143                         fn[fn.length] = 'n = getNodes(n, mode, "*");';
11144                     }
11145                 // type of "simple"
11146                 }else{
11147                     if(tokenMatch){
11148                         if(tokenMatch[1] == "#"){
11149                             fn[fn.length] = 'n = byId(n, "'+tokenMatch[2]+'");';
11150                         }else{
11151                             fn[fn.length] = 'n = byTag(n, "'+tokenMatch[2]+'");';
11152                         }
11153                         path = path.replace(tokenMatch[0], "");
11154                     }
11155                 }
11156                 while(!(modeMatch = path.match(modeRe))){
11157                     var matched = false;
11158                     for(var j = 0; j < matchersLn; j++){
11159                         var t = matchers[j];
11160                         var m = path.match(t.re);
11161                         if(m){
11162                             fn[fn.length] = t.select.replace(tplRe, function(x, i){
11163                                 return m[i];
11164                             });
11165                             path = path.replace(m[0], "");
11166                             matched = true;
11167                             break;
11168                         }
11169                     }
11170                     // prevent infinite loop on bad selector
11171                     if(!matched){
11172                         Ext.Error.raise({
11173                             sourceClass: 'Ext.DomQuery',
11174                             sourceMethod: 'compile',
11175                             msg: 'Error parsing selector. Parsing failed at "' + path + '"'
11176                         });
11177                     }
11178                 }
11179                 if(modeMatch[1]){
11180                     fn[fn.length] = 'mode="'+modeMatch[1].replace(trimRe, "")+'";';
11181                     path = path.replace(modeMatch[1], "");
11182                 }
11183             }
11184             // close fn out
11185             fn[fn.length] = "return nodup(n);\n}";
11186
11187             // eval fn and return it
11188             eval(fn.join(""));
11189             return f;
11190         },
11191
11192         /**
11193          * Selects a group of elements.
11194          * @param {String} selector The selector/xpath query (can be a comma separated list of selectors)
11195          * @param {Node/String} root (optional) The start of the query (defaults to document).
11196          * @return {Array} An Array of DOM elements which match the selector. If there are
11197          * no matches, and empty Array is returned.
11198          */
11199         jsSelect: function(path, root, type){
11200             // set root to doc if not specified.
11201             root = root || document;
11202
11203             if(typeof root == "string"){
11204                 root = document.getElementById(root);
11205             }
11206             var paths = path.split(","),
11207                 results = [];
11208
11209             // loop over each selector
11210             for(var i = 0, len = paths.length; i < len; i++){
11211                 var subPath = paths[i].replace(trimRe, "");
11212                 // compile and place in cache
11213                 if(!cache[subPath]){
11214                     cache[subPath] = Ext.DomQuery.compile(subPath);
11215                     if(!cache[subPath]){
11216                         Ext.Error.raise({
11217                             sourceClass: 'Ext.DomQuery',
11218                             sourceMethod: 'jsSelect',
11219                             msg: subPath + ' is not a valid selector'
11220                         });
11221                     }
11222                 }
11223                 var result = cache[subPath](root);
11224                 if(result && result != document){
11225                     results = results.concat(result);
11226                 }
11227             }
11228
11229             // if there were multiple selectors, make sure dups
11230             // are eliminated
11231             if(paths.length > 1){
11232                 return nodup(results);
11233             }
11234             return results;
11235         },
11236
11237         isXml: function(el) {
11238             var docEl = (el ? el.ownerDocument || el : 0).documentElement;
11239             return docEl ? docEl.nodeName !== "HTML" : false;
11240         },
11241         
11242         select : document.querySelectorAll ? function(path, root, type) {
11243             root = root || document;
11244             if (!Ext.DomQuery.isXml(root)) {
11245             try {
11246                 var cs = root.querySelectorAll(path);
11247                 return Ext.Array.toArray(cs);
11248             }
11249             catch (ex) {}
11250             }
11251             return Ext.DomQuery.jsSelect.call(this, path, root, type);
11252         } : function(path, root, type) {
11253             return Ext.DomQuery.jsSelect.call(this, path, root, type);
11254         },
11255
11256         /**
11257          * Selects a single element.
11258          * @param {String} selector The selector/xpath query
11259          * @param {Node} root (optional) The start of the query (defaults to document).
11260          * @return {Element} The DOM element which matched the selector.
11261          */
11262         selectNode : function(path, root){
11263             return Ext.DomQuery.select(path, root)[0];
11264         },
11265
11266         /**
11267          * Selects the value of a node, optionally replacing null with the defaultValue.
11268          * @param {String} selector The selector/xpath query
11269          * @param {Node} root (optional) The start of the query (defaults to document).
11270          * @param {String} defaultValue
11271          * @return {String}
11272          */
11273         selectValue : function(path, root, defaultValue){
11274             path = path.replace(trimRe, "");
11275             if(!valueCache[path]){
11276                 valueCache[path] = Ext.DomQuery.compile(path, "select");
11277             }
11278             var n = valueCache[path](root), v;
11279             n = n[0] ? n[0] : n;
11280
11281             // overcome a limitation of maximum textnode size
11282             // Rumored to potentially crash IE6 but has not been confirmed.
11283             // http://reference.sitepoint.com/javascript/Node/normalize
11284             // https://developer.mozilla.org/En/DOM/Node.normalize
11285             if (typeof n.normalize == 'function') n.normalize();
11286
11287             v = (n && n.firstChild ? n.firstChild.nodeValue : null);
11288             return ((v === null||v === undefined||v==='') ? defaultValue : v);
11289         },
11290
11291         /**
11292          * Selects the value of a node, parsing integers and floats. Returns the defaultValue, or 0 if none is specified.
11293          * @param {String} selector The selector/xpath query
11294          * @param {Node} root (optional) The start of the query (defaults to document).
11295          * @param {Number} defaultValue
11296          * @return {Number}
11297          */
11298         selectNumber : function(path, root, defaultValue){
11299             var v = Ext.DomQuery.selectValue(path, root, defaultValue || 0);
11300             return parseFloat(v);
11301         },
11302
11303         /**
11304          * Returns true if the passed element(s) match the passed simple selector (e.g. div.some-class or span:first-child)
11305          * @param {String/HTMLElement/Array} el An element id, element or array of elements
11306          * @param {String} selector The simple selector to test
11307          * @return {Boolean}
11308          */
11309         is : function(el, ss){
11310             if(typeof el == "string"){
11311                 el = document.getElementById(el);
11312             }
11313             var isArray = Ext.isArray(el),
11314                 result = Ext.DomQuery.filter(isArray ? el : [el], ss);
11315             return isArray ? (result.length == el.length) : (result.length > 0);
11316         },
11317
11318         /**
11319          * Filters an array of elements to only include matches of a simple selector (e.g. div.some-class or span:first-child)
11320          * @param {Array} el An array of elements to filter
11321          * @param {String} selector The simple selector to test
11322          * @param {Boolean} nonMatches If true, it returns the elements that DON'T match
11323          * the selector instead of the ones that match
11324          * @return {Array} An Array of DOM elements which match the selector. If there are
11325          * no matches, and empty Array is returned.
11326          */
11327         filter : function(els, ss, nonMatches){
11328             ss = ss.replace(trimRe, "");
11329             if(!simpleCache[ss]){
11330                 simpleCache[ss] = Ext.DomQuery.compile(ss, "simple");
11331             }
11332             var result = simpleCache[ss](els);
11333             return nonMatches ? quickDiff(result, els) : result;
11334         },
11335
11336         /**
11337          * Collection of matching regular expressions and code snippets.
11338          * Each capture group within () will be replace the {} in the select
11339          * statement as specified by their index.
11340          */
11341         matchers : [{
11342                 re: /^\.([\w-]+)/,
11343                 select: 'n = byClassName(n, " {1} ");'
11344             }, {
11345                 re: /^\:([\w-]+)(?:\(((?:[^\s>\/]*|.*?))\))?/,
11346                 select: 'n = byPseudo(n, "{1}", "{2}");'
11347             },{
11348                 re: /^(?:([\[\{])(?:@)?([\w-]+)\s?(?:(=|.=)\s?['"]?(.*?)["']?)?[\]\}])/,
11349                 select: 'n = byAttribute(n, "{2}", "{4}", "{3}", "{1}");'
11350             }, {
11351                 re: /^#([\w-]+)/,
11352                 select: 'n = byId(n, "{1}");'
11353             },{
11354                 re: /^@([\w-]+)/,
11355                 select: 'return {firstChild:{nodeValue:attrValue(n, "{1}")}};'
11356             }
11357         ],
11358
11359         /**
11360          * Collection of operator comparison functions. The default operators are =, !=, ^=, $=, *=, %=, |= and ~=.
11361          * 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;.
11362          */
11363         operators : {
11364             "=" : function(a, v){
11365                 return a == v;
11366             },
11367             "!=" : function(a, v){
11368                 return a != v;
11369             },
11370             "^=" : function(a, v){
11371                 return a && a.substr(0, v.length) == v;
11372             },
11373             "$=" : function(a, v){
11374                 return a && a.substr(a.length-v.length) == v;
11375             },
11376             "*=" : function(a, v){
11377                 return a && a.indexOf(v) !== -1;
11378             },
11379             "%=" : function(a, v){
11380                 return (a % v) == 0;
11381             },
11382             "|=" : function(a, v){
11383                 return a && (a == v || a.substr(0, v.length+1) == v+'-');
11384             },
11385             "~=" : function(a, v){
11386                 return a && (' '+a+' ').indexOf(' '+v+' ') != -1;
11387             }
11388         },
11389
11390         /**
11391 Object hash of "pseudo class" filter functions which are used when filtering selections. 
11392 Each function is passed two parameters:
11393
11394 - **c** : Array
11395     An Array of DOM elements to filter.
11396     
11397 - **v** : String
11398     The argument (if any) supplied in the selector.
11399
11400 A filter function returns an Array of DOM elements which conform to the pseudo class.
11401 In addition to the provided pseudo classes listed above such as `first-child` and `nth-child`,
11402 developers may add additional, custom psuedo class filters to select elements according to application-specific requirements.
11403
11404 For example, to filter `a` elements to only return links to __external__ resources:
11405
11406     Ext.DomQuery.pseudos.external = function(c, v){
11407         var r = [], ri = -1;
11408         for(var i = 0, ci; ci = c[i]; i++){
11409             // Include in result set only if it's a link to an external resource
11410             if(ci.hostname != location.hostname){
11411                 r[++ri] = ci;
11412             }
11413         }
11414         return r;
11415     };
11416
11417 Then external links could be gathered with the following statement:
11418
11419     var externalLinks = Ext.select("a:external");
11420
11421         * @markdown
11422         */
11423         pseudos : {
11424             "first-child" : function(c){
11425                 var r = [], ri = -1, n;
11426                 for(var i = 0, ci; ci = n = c[i]; i++){
11427                     while((n = n.previousSibling) && n.nodeType != 1);
11428                     if(!n){
11429                         r[++ri] = ci;
11430                     }
11431                 }
11432                 return r;
11433             },
11434
11435             "last-child" : function(c){
11436                 var r = [], ri = -1, n;
11437                 for(var i = 0, ci; ci = n = c[i]; i++){
11438                     while((n = n.nextSibling) && n.nodeType != 1);
11439                     if(!n){
11440                         r[++ri] = ci;
11441                     }
11442                 }
11443                 return r;
11444             },
11445
11446             "nth-child" : function(c, a) {
11447                 var r = [], ri = -1,
11448                     m = nthRe.exec(a == "even" && "2n" || a == "odd" && "2n+1" || !nthRe2.test(a) && "n+" + a || a),
11449                     f = (m[1] || 1) - 0, l = m[2] - 0;
11450                 for(var i = 0, n; n = c[i]; i++){
11451                     var pn = n.parentNode;
11452                     if (batch != pn._batch) {
11453                         var j = 0;
11454                         for(var cn = pn.firstChild; cn; cn = cn.nextSibling){
11455                             if(cn.nodeType == 1){
11456                                cn.nodeIndex = ++j;
11457                             }
11458                         }
11459                         pn._batch = batch;
11460                     }
11461                     if (f == 1) {
11462                         if (l == 0 || n.nodeIndex == l){
11463                             r[++ri] = n;
11464                         }
11465                     } else if ((n.nodeIndex + l) % f == 0){
11466                         r[++ri] = n;
11467                     }
11468                 }
11469
11470                 return r;
11471             },
11472
11473             "only-child" : function(c){
11474                 var r = [], ri = -1;;
11475                 for(var i = 0, ci; ci = c[i]; i++){
11476                     if(!prev(ci) && !next(ci)){
11477                         r[++ri] = ci;
11478                     }
11479                 }
11480                 return r;
11481             },
11482
11483             "empty" : function(c){
11484                 var r = [], ri = -1;
11485                 for(var i = 0, ci; ci = c[i]; i++){
11486                     var cns = ci.childNodes, j = 0, cn, empty = true;
11487                     while(cn = cns[j]){
11488                         ++j;
11489                         if(cn.nodeType == 1 || cn.nodeType == 3){
11490                             empty = false;
11491                             break;
11492                         }
11493                     }
11494                     if(empty){
11495                         r[++ri] = ci;
11496                     }
11497                 }
11498                 return r;
11499             },
11500
11501             "contains" : function(c, v){
11502                 var r = [], ri = -1;
11503                 for(var i = 0, ci; ci = c[i]; i++){
11504                     if((ci.textContent||ci.innerText||'').indexOf(v) != -1){
11505                         r[++ri] = ci;
11506                     }
11507                 }
11508                 return r;
11509             },
11510
11511             "nodeValue" : function(c, v){
11512                 var r = [], ri = -1;
11513                 for(var i = 0, ci; ci = c[i]; i++){
11514                     if(ci.firstChild && ci.firstChild.nodeValue == v){
11515                         r[++ri] = ci;
11516                     }
11517                 }
11518                 return r;
11519             },
11520
11521             "checked" : function(c){
11522                 var r = [], ri = -1;
11523                 for(var i = 0, ci; ci = c[i]; i++){
11524                     if(ci.checked == true){
11525                         r[++ri] = ci;
11526                     }
11527                 }
11528                 return r;
11529             },
11530
11531             "not" : function(c, ss){
11532                 return Ext.DomQuery.filter(c, ss, true);
11533             },
11534
11535             "any" : function(c, selectors){
11536                 var ss = selectors.split('|'),
11537                     r = [], ri = -1, s;
11538                 for(var i = 0, ci; ci = c[i]; i++){
11539                     for(var j = 0; s = ss[j]; j++){
11540                         if(Ext.DomQuery.is(ci, s)){
11541                             r[++ri] = ci;
11542                             break;
11543                         }
11544                     }
11545                 }
11546                 return r;
11547             },
11548
11549             "odd" : function(c){
11550                 return this["nth-child"](c, "odd");
11551             },
11552
11553             "even" : function(c){
11554                 return this["nth-child"](c, "even");
11555             },
11556
11557             "nth" : function(c, a){
11558                 return c[a-1] || [];
11559             },
11560
11561             "first" : function(c){
11562                 return c[0] || [];
11563             },
11564
11565             "last" : function(c){
11566                 return c[c.length-1] || [];
11567             },
11568
11569             "has" : function(c, ss){
11570                 var s = Ext.DomQuery.select,
11571                     r = [], ri = -1;
11572                 for(var i = 0, ci; ci = c[i]; i++){
11573                     if(s(ss, ci).length > 0){
11574                         r[++ri] = ci;
11575                     }
11576                 }
11577                 return r;
11578             },
11579
11580             "next" : function(c, ss){
11581                 var is = Ext.DomQuery.is,
11582                     r = [], ri = -1;
11583                 for(var i = 0, ci; ci = c[i]; i++){
11584                     var n = next(ci);
11585                     if(n && is(n, ss)){
11586                         r[++ri] = ci;
11587                     }
11588                 }
11589                 return r;
11590             },
11591
11592             "prev" : function(c, ss){
11593                 var is = Ext.DomQuery.is,
11594                     r = [], ri = -1;
11595                 for(var i = 0, ci; ci = c[i]; i++){
11596                     var n = prev(ci);
11597                     if(n && is(n, ss)){
11598                         r[++ri] = ci;
11599                     }
11600                 }
11601                 return r;
11602             }
11603         }
11604     };
11605 }();
11606
11607 /**
11608  * Selects an array of DOM nodes by CSS/XPath selector. Shorthand of {@link Ext.DomQuery#select}
11609  * @param {String} path The selector/xpath query
11610  * @param {Node} root (optional) The start of the query (defaults to document).
11611  * @return {Array}
11612  * @member Ext
11613  * @method query
11614  */
11615 Ext.query = Ext.DomQuery.select;
11616
11617 /**
11618  * @class Ext.core.Element
11619  * <p>Encapsulates a DOM element, adding simple DOM manipulation facilities, normalizing for browser differences.</p>
11620  * <p>All instances of this class inherit the methods of {@link Ext.fx.Anim} making visual effects easily available to all DOM elements.</p>
11621  * <p>Note that the events documented in this class are not Ext events, they encapsulate browser events. To
11622  * access the underlying browser event, see {@link Ext.EventObject#browserEvent}. Some older
11623  * browsers may not support the full range of events. Which events are supported is beyond the control of ExtJs.</p>
11624  * Usage:<br>
11625 <pre><code>
11626 // by id
11627 var el = Ext.get("my-div");
11628
11629 // by DOM element reference
11630 var el = Ext.get(myDivElement);
11631 </code></pre>
11632  * <b>Animations</b><br />
11633  * <p>When an element is manipulated, by default there is no animation.</p>
11634  * <pre><code>
11635 var el = Ext.get("my-div");
11636
11637 // no animation
11638 el.setWidth(100);
11639  * </code></pre>
11640  * <p>Many of the functions for manipulating an element have an optional "animate" parameter.  This
11641  * parameter can be specified as boolean (<tt>true</tt>) for default animation effects.</p>
11642  * <pre><code>
11643 // default animation
11644 el.setWidth(100, true);
11645  * </code></pre>
11646  *
11647  * <p>To configure the effects, an object literal with animation options to use as the Element animation
11648  * configuration object can also be specified. Note that the supported Element animation configuration
11649  * options are a subset of the {@link Ext.fx.Anim} animation options specific to Fx effects.  The supported
11650  * Element animation configuration options are:</p>
11651 <pre>
11652 Option    Default   Description
11653 --------- --------  ---------------------------------------------
11654 {@link Ext.fx.Anim#duration duration}  .35       The duration of the animation in seconds
11655 {@link Ext.fx.Anim#easing easing}    easeOut   The easing method
11656 {@link Ext.fx.Anim#callback callback}  none      A function to execute when the anim completes
11657 {@link Ext.fx.Anim#scope scope}     this      The scope (this) of the callback function
11658 </pre>
11659  *
11660  * <pre><code>
11661 // Element animation options object
11662 var opt = {
11663     {@link Ext.fx.Anim#duration duration}: 1,
11664     {@link Ext.fx.Anim#easing easing}: 'elasticIn',
11665     {@link Ext.fx.Anim#callback callback}: this.foo,
11666     {@link Ext.fx.Anim#scope scope}: this
11667 };
11668 // animation with some options set
11669 el.setWidth(100, opt);
11670  * </code></pre>
11671  * <p>The Element animation object being used for the animation will be set on the options
11672  * object as "anim", which allows you to stop or manipulate the animation. Here is an example:</p>
11673  * <pre><code>
11674 // using the "anim" property to get the Anim object
11675 if(opt.anim.isAnimated()){
11676     opt.anim.stop();
11677 }
11678  * </code></pre>
11679  * <p>Also see the <tt>{@link #animate}</tt> method for another animation technique.</p>
11680  * <p><b> Composite (Collections of) Elements</b></p>
11681  * <p>For working with collections of Elements, see {@link Ext.CompositeElement}</p>
11682  * @constructor Create a new Element directly.
11683  * @param {String/HTMLElement} element
11684  * @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).
11685  */
11686  (function() {
11687     var DOC = document,
11688         EC = Ext.cache;
11689
11690     Ext.Element = Ext.core.Element = function(element, forceNew) {
11691         var dom = typeof element == "string" ? DOC.getElementById(element) : element,
11692         id;
11693
11694         if (!dom) {
11695             return null;
11696         }
11697
11698         id = dom.id;
11699
11700         if (!forceNew && id && EC[id]) {
11701             // element object already exists
11702             return EC[id].el;
11703         }
11704
11705         /**
11706      * The DOM element
11707      * @type HTMLElement
11708      */
11709         this.dom = dom;
11710
11711         /**
11712      * The DOM element ID
11713      * @type String
11714      */
11715         this.id = id || Ext.id(dom);
11716     };
11717
11718     var DH = Ext.core.DomHelper,
11719     El = Ext.core.Element;
11720
11721
11722     El.prototype = {
11723         /**
11724      * Sets the passed attributes as attributes of this element (a style attribute can be a string, object or function)
11725      * @param {Object} o The object with the attributes
11726      * @param {Boolean} useSet (optional) false to override the default setAttribute to use expandos.
11727      * @return {Ext.core.Element} this
11728      */
11729         set: function(o, useSet) {
11730             var el = this.dom,
11731                 attr,
11732                 val;
11733             useSet = (useSet !== false) && !!el.setAttribute;
11734
11735             for (attr in o) {
11736                 if (o.hasOwnProperty(attr)) {
11737                     val = o[attr];
11738                     if (attr == 'style') {
11739                         DH.applyStyles(el, val);
11740                     } else if (attr == 'cls') {
11741                         el.className = val;
11742                     } else if (useSet) {
11743                         el.setAttribute(attr, val);
11744                     } else {
11745                         el[attr] = val;
11746                     }
11747                 }
11748             }
11749             return this;
11750         },
11751
11752         //  Mouse events
11753         /**
11754      * @event click
11755      * Fires when a mouse click is detected within the element.
11756      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
11757      * @param {HtmlElement} t The target of the event.
11758      * @param {Object} o The options configuration passed to the {@link #addListener} call.
11759      */
11760         /**
11761      * @event contextmenu
11762      * Fires when a right click is detected within the element.
11763      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
11764      * @param {HtmlElement} t The target of the event.
11765      * @param {Object} o The options configuration passed to the {@link #addListener} call.
11766      */
11767         /**
11768      * @event dblclick
11769      * Fires when a mouse double click is detected within the element.
11770      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
11771      * @param {HtmlElement} t The target of the event.
11772      * @param {Object} o The options configuration passed to the {@link #addListener} call.
11773      */
11774         /**
11775      * @event mousedown
11776      * Fires when a mousedown is detected within the element.
11777      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
11778      * @param {HtmlElement} t The target of the event.
11779      * @param {Object} o The options configuration passed to the {@link #addListener} call.
11780      */
11781         /**
11782      * @event mouseup
11783      * Fires when a mouseup is detected within the element.
11784      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
11785      * @param {HtmlElement} t The target of the event.
11786      * @param {Object} o The options configuration passed to the {@link #addListener} call.
11787      */
11788         /**
11789      * @event mouseover
11790      * Fires when a mouseover is detected within the element.
11791      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
11792      * @param {HtmlElement} t The target of the event.
11793      * @param {Object} o The options configuration passed to the {@link #addListener} call.
11794      */
11795         /**
11796      * @event mousemove
11797      * Fires when a mousemove is detected with the element.
11798      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
11799      * @param {HtmlElement} t The target of the event.
11800      * @param {Object} o The options configuration passed to the {@link #addListener} call.
11801      */
11802         /**
11803      * @event mouseout
11804      * Fires when a mouseout is detected with the element.
11805      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
11806      * @param {HtmlElement} t The target of the event.
11807      * @param {Object} o The options configuration passed to the {@link #addListener} call.
11808      */
11809         /**
11810      * @event mouseenter
11811      * Fires when the mouse enters the element.
11812      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
11813      * @param {HtmlElement} t The target of the event.
11814      * @param {Object} o The options configuration passed to the {@link #addListener} call.
11815      */
11816         /**
11817      * @event mouseleave
11818      * Fires when the mouse leaves the element.
11819      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
11820      * @param {HtmlElement} t The target of the event.
11821      * @param {Object} o The options configuration passed to the {@link #addListener} call.
11822      */
11823
11824         //  Keyboard events
11825         /**
11826      * @event keypress
11827      * Fires when a keypress is detected within the element.
11828      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
11829      * @param {HtmlElement} t The target of the event.
11830      * @param {Object} o The options configuration passed to the {@link #addListener} call.
11831      */
11832         /**
11833      * @event keydown
11834      * Fires when a keydown is detected within the element.
11835      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
11836      * @param {HtmlElement} t The target of the event.
11837      * @param {Object} o The options configuration passed to the {@link #addListener} call.
11838      */
11839         /**
11840      * @event keyup
11841      * Fires when a keyup is detected within the element.
11842      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
11843      * @param {HtmlElement} t The target of the event.
11844      * @param {Object} o The options configuration passed to the {@link #addListener} call.
11845      */
11846
11847
11848         //  HTML frame/object events
11849         /**
11850      * @event load
11851      * Fires when the user agent finishes loading all content within the element. Only supported by window, frames, objects and images.
11852      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
11853      * @param {HtmlElement} t The target of the event.
11854      * @param {Object} o The options configuration passed to the {@link #addListener} call.
11855      */
11856         /**
11857      * @event unload
11858      * 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.
11859      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
11860      * @param {HtmlElement} t The target of the event.
11861      * @param {Object} o The options configuration passed to the {@link #addListener} call.
11862      */
11863         /**
11864      * @event abort
11865      * Fires when an object/image is stopped from loading before completely loaded.
11866      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
11867      * @param {HtmlElement} t The target of the event.
11868      * @param {Object} o The options configuration passed to the {@link #addListener} call.
11869      */
11870         /**
11871      * @event error
11872      * Fires when an object/image/frame cannot be loaded properly.
11873      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
11874      * @param {HtmlElement} t The target of the event.
11875      * @param {Object} o The options configuration passed to the {@link #addListener} call.
11876      */
11877         /**
11878      * @event resize
11879      * Fires when a document view is resized.
11880      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
11881      * @param {HtmlElement} t The target of the event.
11882      * @param {Object} o The options configuration passed to the {@link #addListener} call.
11883      */
11884         /**
11885      * @event scroll
11886      * Fires when a document view is scrolled.
11887      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
11888      * @param {HtmlElement} t The target of the event.
11889      * @param {Object} o The options configuration passed to the {@link #addListener} call.
11890      */
11891
11892         //  Form events
11893         /**
11894      * @event select
11895      * Fires when a user selects some text in a text field, including input and textarea.
11896      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
11897      * @param {HtmlElement} t The target of the event.
11898      * @param {Object} o The options configuration passed to the {@link #addListener} call.
11899      */
11900         /**
11901      * @event change
11902      * Fires when a control loses the input focus and its value has been modified since gaining focus.
11903      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
11904      * @param {HtmlElement} t The target of the event.
11905      * @param {Object} o The options configuration passed to the {@link #addListener} call.
11906      */
11907         /**
11908      * @event submit
11909      * Fires when a form is submitted.
11910      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
11911      * @param {HtmlElement} t The target of the event.
11912      * @param {Object} o The options configuration passed to the {@link #addListener} call.
11913      */
11914         /**
11915      * @event reset
11916      * Fires when a form is reset.
11917      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
11918      * @param {HtmlElement} t The target of the event.
11919      * @param {Object} o The options configuration passed to the {@link #addListener} call.
11920      */
11921         /**
11922      * @event focus
11923      * Fires when an element receives focus either via the pointing device or by tab navigation.
11924      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
11925      * @param {HtmlElement} t The target of the event.
11926      * @param {Object} o The options configuration passed to the {@link #addListener} call.
11927      */
11928         /**
11929      * @event blur
11930      * Fires when an element loses focus either via the pointing device or by tabbing navigation.
11931      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
11932      * @param {HtmlElement} t The target of the event.
11933      * @param {Object} o The options configuration passed to the {@link #addListener} call.
11934      */
11935
11936         //  User Interface events
11937         /**
11938      * @event DOMFocusIn
11939      * Where supported. Similar to HTML focus event, but can be applied to any focusable element.
11940      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
11941      * @param {HtmlElement} t The target of the event.
11942      * @param {Object} o The options configuration passed to the {@link #addListener} call.
11943      */
11944         /**
11945      * @event DOMFocusOut
11946      * Where supported. Similar to HTML blur event, but can be applied to any focusable element.
11947      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
11948      * @param {HtmlElement} t The target of the event.
11949      * @param {Object} o The options configuration passed to the {@link #addListener} call.
11950      */
11951         /**
11952      * @event DOMActivate
11953      * Where supported. Fires when an element is activated, for instance, through a mouse click or a keypress.
11954      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
11955      * @param {HtmlElement} t The target of the event.
11956      * @param {Object} o The options configuration passed to the {@link #addListener} call.
11957      */
11958
11959         //  DOM Mutation events
11960         /**
11961      * @event DOMSubtreeModified
11962      * Where supported. Fires when the subtree is modified.
11963      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
11964      * @param {HtmlElement} t The target of the event.
11965      * @param {Object} o The options configuration passed to the {@link #addListener} call.
11966      */
11967         /**
11968      * @event DOMNodeInserted
11969      * Where supported. Fires when a node has been added as a child of another node.
11970      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
11971      * @param {HtmlElement} t The target of the event.
11972      * @param {Object} o The options configuration passed to the {@link #addListener} call.
11973      */
11974         /**
11975      * @event DOMNodeRemoved
11976      * Where supported. Fires when a descendant node of the element is removed.
11977      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
11978      * @param {HtmlElement} t The target of the event.
11979      * @param {Object} o The options configuration passed to the {@link #addListener} call.
11980      */
11981         /**
11982      * @event DOMNodeRemovedFromDocument
11983      * Where supported. Fires when a node is being removed from a document.
11984      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
11985      * @param {HtmlElement} t The target of the event.
11986      * @param {Object} o The options configuration passed to the {@link #addListener} call.
11987      */
11988         /**
11989      * @event DOMNodeInsertedIntoDocument
11990      * Where supported. Fires when a node is being inserted into a document.
11991      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
11992      * @param {HtmlElement} t The target of the event.
11993      * @param {Object} o The options configuration passed to the {@link #addListener} call.
11994      */
11995         /**
11996      * @event DOMAttrModified
11997      * Where supported. Fires when an attribute has been modified.
11998      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
11999      * @param {HtmlElement} t The target of the event.
12000      * @param {Object} o The options configuration passed to the {@link #addListener} call.
12001      */
12002         /**
12003      * @event DOMCharacterDataModified
12004      * Where supported. Fires when the character data has been modified.
12005      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12006      * @param {HtmlElement} t The target of the event.
12007      * @param {Object} o The options configuration passed to the {@link #addListener} call.
12008      */
12009
12010         /**
12011      * The default unit to append to CSS values where a unit isn't provided (defaults to px).
12012      * @type String
12013      */
12014         defaultUnit: "px",
12015
12016         /**
12017      * Returns true if this element matches the passed simple selector (e.g. div.some-class or span:first-child)
12018      * @param {String} selector The simple selector to test
12019      * @return {Boolean} True if this element matches the selector, else false
12020      */
12021         is: function(simpleSelector) {
12022             return Ext.DomQuery.is(this.dom, simpleSelector);
12023         },
12024
12025         /**
12026      * Tries to focus the element. Any exceptions are caught and ignored.
12027      * @param {Number} defer (optional) Milliseconds to defer the focus
12028      * @return {Ext.core.Element} this
12029      */
12030         focus: function(defer,
12031                         /* private */
12032                         dom) {
12033             var me = this;
12034             dom = dom || me.dom;
12035             try {
12036                 if (Number(defer)) {
12037                     Ext.defer(me.focus, defer, null, [null, dom]);
12038                 } else {
12039                     dom.focus();
12040                 }
12041             } catch(e) {}
12042             return me;
12043         },
12044
12045         /**
12046      * Tries to blur the element. Any exceptions are caught and ignored.
12047      * @return {Ext.core.Element} this
12048      */
12049         blur: function() {
12050             try {
12051                 this.dom.blur();
12052             } catch(e) {}
12053             return this;
12054         },
12055
12056         /**
12057      * Returns the value of the "value" attribute
12058      * @param {Boolean} asNumber true to parse the value as a number
12059      * @return {String/Number}
12060      */
12061         getValue: function(asNumber) {
12062             var val = this.dom.value;
12063             return asNumber ? parseInt(val, 10) : val;
12064         },
12065
12066         /**
12067      * Appends an event handler to this element.  The shorthand version {@link #on} is equivalent.
12068      * @param {String} eventName The name of event to handle.
12069      * @param {Function} fn The handler function the event invokes. This function is passed
12070      * the following parameters:<ul>
12071      * <li><b>evt</b> : EventObject<div class="sub-desc">The {@link Ext.EventObject EventObject} describing the event.</div></li>
12072      * <li><b>el</b> : HtmlElement<div class="sub-desc">The DOM element which was the target of the event.
12073      * Note that this may be filtered by using the <tt>delegate</tt> option.</div></li>
12074      * <li><b>o</b> : Object<div class="sub-desc">The options object from the addListener call.</div></li>
12075      * </ul>
12076      * @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which the handler function is executed.
12077      * <b>If omitted, defaults to this Element.</b>.
12078      * @param {Object} options (optional) An object containing handler configuration properties.
12079      * This may contain any of the following properties:<ul>
12080      * <li><b>scope</b> Object : <div class="sub-desc">The scope (<code><b>this</b></code> reference) in which the handler function is executed.
12081      * <b>If omitted, defaults to this Element.</b></div></li>
12082      * <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>
12083      * <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>
12084      * <li><b>preventDefault</b> Boolean: <div class="sub-desc">True to prevent the default action</div></li>
12085      * <li><b>stopPropagation</b> Boolean: <div class="sub-desc">True to prevent event propagation</div></li>
12086      * <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>
12087      * <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>
12088      * <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>
12089      * <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>
12090      * <li><b>buffer</b> Number: <div class="sub-desc">Causes the handler to be scheduled to run in an {@link Ext.util.DelayedTask} delayed
12091      * by the specified number of milliseconds. If the event fires again within that time, the original
12092      * handler is <em>not</em> invoked, but the new handler is scheduled in its place.</div></li>
12093      * </ul><br>
12094      * <p>
12095      * <b>Combining Options</b><br>
12096      * In the following examples, the shorthand form {@link #on} is used rather than the more verbose
12097      * addListener.  The two are equivalent.  Using the options argument, it is possible to combine different
12098      * types of listeners:<br>
12099      * <br>
12100      * A delayed, one-time listener that auto stops the event and adds a custom argument (forumId) to the
12101      * options object. The options object is available as the third parameter in the handler function.<div style="margin: 5px 20px 20px;">
12102      * Code:<pre><code>
12103 el.on('click', this.onClick, this, {
12104     single: true,
12105     delay: 100,
12106     stopEvent : true,
12107     forumId: 4
12108 });</code></pre></p>
12109      * <p>
12110      * <b>Attaching multiple handlers in 1 call</b><br>
12111      * The method also allows for a single argument to be passed which is a config object containing properties
12112      * which specify multiple handlers.</p>
12113      * <p>
12114      * Code:<pre><code>
12115 el.on({
12116     'click' : {
12117         fn: this.onClick,
12118         scope: this,
12119         delay: 100
12120     },
12121     'mouseover' : {
12122         fn: this.onMouseOver,
12123         scope: this
12124     },
12125     'mouseout' : {
12126         fn: this.onMouseOut,
12127         scope: this
12128     }
12129 });</code></pre>
12130      * <p>
12131      * Or a shorthand syntax:<br>
12132      * Code:<pre><code></p>
12133 el.on({
12134     'click' : this.onClick,
12135     'mouseover' : this.onMouseOver,
12136     'mouseout' : this.onMouseOut,
12137     scope: this
12138 });
12139      * </code></pre></p>
12140      * <p><b>delegate</b></p>
12141      * <p>This is a configuration option that you can pass along when registering a handler for
12142      * an event to assist with event delegation. Event delegation is a technique that is used to
12143      * reduce memory consumption and prevent exposure to memory-leaks. By registering an event
12144      * for a container element as opposed to each element within a container. By setting this
12145      * configuration option to a simple selector, the target element will be filtered to look for
12146      * a descendant of the target.
12147      * For example:<pre><code>
12148 // using this markup:
12149 &lt;div id='elId'>
12150     &lt;p id='p1'>paragraph one&lt;/p>
12151     &lt;p id='p2' class='clickable'>paragraph two&lt;/p>
12152     &lt;p id='p3'>paragraph three&lt;/p>
12153 &lt;/div>
12154 // utilize event delegation to registering just one handler on the container element:
12155 el = Ext.get('elId');
12156 el.on(
12157     'click',
12158     function(e,t) {
12159         // handle click
12160         console.info(t.id); // 'p2'
12161     },
12162     this,
12163     {
12164         // filter the target element to be a descendant with the class 'clickable'
12165         delegate: '.clickable'
12166     }
12167 );
12168      * </code></pre></p>
12169      * @return {Ext.core.Element} this
12170      */
12171         addListener: function(eventName, fn, scope, options) {
12172             Ext.EventManager.on(this.dom, eventName, fn, scope || this, options);
12173             return this;
12174         },
12175
12176         /**
12177      * Removes an event handler from this element.  The shorthand version {@link #un} is equivalent.
12178      * <b>Note</b>: if a <i>scope</i> was explicitly specified when {@link #addListener adding} the
12179      * listener, the same scope must be specified here.
12180      * Example:
12181      * <pre><code>
12182 el.removeListener('click', this.handlerFn);
12183 // or
12184 el.un('click', this.handlerFn);
12185 </code></pre>
12186      * @param {String} eventName The name of the event from which to remove the handler.
12187      * @param {Function} fn The handler function to remove. <b>This must be a reference to the function passed into the {@link #addListener} call.</b>
12188      * @param {Object} scope If a scope (<b><code>this</code></b> reference) was specified when the listener was added,
12189      * then this must refer to the same object.
12190      * @return {Ext.core.Element} this
12191      */
12192         removeListener: function(eventName, fn, scope) {
12193             Ext.EventManager.un(this.dom, eventName, fn, scope || this);
12194             return this;
12195         },
12196
12197         /**
12198      * Removes all previous added listeners from this element
12199      * @return {Ext.core.Element} this
12200      */
12201         removeAllListeners: function() {
12202             Ext.EventManager.removeAll(this.dom);
12203             return this;
12204         },
12205
12206         /**
12207          * Recursively removes all previous added listeners from this element and its children
12208          * @return {Ext.core.Element} this
12209          */
12210         purgeAllListeners: function() {
12211             Ext.EventManager.purgeElement(this);
12212             return this;
12213         },
12214
12215         /**
12216          * @private Test if size has a unit, otherwise appends the passed unit string, or the default for this Element.
12217          * @param size {Mixed} The size to set
12218          * @param units {String} The units to append to a numeric size value
12219          */
12220         addUnits: function(size, units) {
12221
12222             // Most common case first: Size is set to a number
12223             if (Ext.isNumber(size)) {
12224                 return size + (units || this.defaultUnit || 'px');
12225             }
12226
12227             // Size set to a value which means "auto"
12228             if (size === "" || size == "auto" || size === undefined || size === null) {
12229                 return size || '';
12230             }
12231
12232             // Otherwise, warn if it's not a valid CSS measurement
12233             if (!unitPattern.test(size)) {
12234                 if (Ext.isDefined(Ext.global.console)) {
12235                     Ext.global.console.warn("Warning, size detected as NaN on Element.addUnits.");
12236                 }
12237                 return size || '';
12238             }
12239             return size;
12240         },
12241
12242         /**
12243          * Tests various css rules/browsers to determine if this element uses a border box
12244          * @return {Boolean}
12245          */
12246         isBorderBox: function() {
12247             return Ext.isBorderBox || noBoxAdjust[(this.dom.tagName || "").toLowerCase()];
12248         },
12249
12250         /**
12251          * <p>Removes this element's dom reference.  Note that event and cache removal is handled at {@link Ext#removeNode Ext.removeNode}</p>
12252          */
12253         remove: function() {
12254             var me = this,
12255             dom = me.dom;
12256
12257             if (dom) {
12258                 delete me.dom;
12259                 Ext.removeNode(dom);
12260             }
12261         },
12262
12263         /**
12264          * Sets up event handlers to call the passed functions when the mouse is moved into and out of the Element.
12265          * @param {Function} overFn The function to call when the mouse enters the Element.
12266          * @param {Function} outFn The function to call when the mouse leaves the Element.
12267          * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the functions are executed. Defaults to the Element's DOM element.
12268          * @param {Object} options (optional) Options for the listener. See {@link Ext.util.Observable#addListener the <tt>options</tt> parameter}.
12269          * @return {Ext.core.Element} this
12270          */
12271         hover: function(overFn, outFn, scope, options) {
12272             var me = this;
12273             me.on('mouseenter', overFn, scope || me.dom, options);
12274             me.on('mouseleave', outFn, scope || me.dom, options);
12275             return me;
12276         },
12277
12278         /**
12279          * Returns true if this element is an ancestor of the passed element
12280          * @param {HTMLElement/String} el The element to check
12281          * @return {Boolean} True if this element is an ancestor of el, else false
12282          */
12283         contains: function(el) {
12284             return ! el ? false: Ext.core.Element.isAncestor(this.dom, el.dom ? el.dom: el);
12285         },
12286
12287         /**
12288          * Returns the value of a namespaced attribute from the element's underlying DOM node.
12289          * @param {String} namespace The namespace in which to look for the attribute
12290          * @param {String} name The attribute name
12291          * @return {String} The attribute value
12292          * @deprecated
12293          */
12294         getAttributeNS: function(ns, name) {
12295             return this.getAttribute(name, ns);
12296         },
12297
12298         /**
12299          * Returns the value of an attribute from the element's underlying DOM node.
12300          * @param {String} name The attribute name
12301          * @param {String} namespace (optional) The namespace in which to look for the attribute
12302          * @return {String} The attribute value
12303          */
12304         getAttribute: (Ext.isIE && !(Ext.isIE9 && document.documentMode === 9)) ?
12305         function(name, ns) {
12306             var d = this.dom,
12307             type;
12308             if(ns) {
12309                 type = typeof d[ns + ":" + name];
12310                 if (type != 'undefined' && type != 'unknown') {
12311                     return d[ns + ":" + name] || null;
12312                 }
12313                 return null;
12314             }
12315             if (name === "for") {
12316                 name = "htmlFor";
12317             }
12318             return d[name] || null;
12319         }: function(name, ns) {
12320             var d = this.dom;
12321             if (ns) {
12322                return d.getAttributeNS(ns, name) || d.getAttribute(ns + ":" + name);
12323             }
12324             return  d.getAttribute(name) || d[name] || null;
12325         },
12326
12327         /**
12328          * Update the innerHTML of this element
12329          * @param {String} html The new HTML
12330          * @return {Ext.core.Element} this
12331          */
12332         update: function(html) {
12333             if (this.dom) {
12334                 this.dom.innerHTML = html;
12335             }
12336             return this;
12337         }
12338     };
12339
12340     var ep = El.prototype;
12341
12342     El.addMethods = function(o) {
12343         Ext.apply(ep, o);
12344     };
12345
12346     /**
12347      * Appends an event handler (shorthand for {@link #addListener}).
12348      * @param {String} eventName The name of event to handle.
12349      * @param {Function} fn The handler function the event invokes.
12350      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the handler function is executed.
12351      * @param {Object} options (optional) An object containing standard {@link #addListener} options
12352      * @member Ext.core.Element
12353      * @method on
12354      */
12355     ep.on = ep.addListener;
12356
12357     /**
12358      * Removes an event handler from this element (see {@link #removeListener} for additional notes).
12359      * @param {String} eventName The name of the event from which to remove the handler.
12360      * @param {Function} fn The handler function to remove. <b>This must be a reference to the function passed into the {@link #addListener} call.</b>
12361      * @param {Object} scope If a scope (<b><code>this</code></b> reference) was specified when the listener was added,
12362      * then this must refer to the same object.
12363      * @return {Ext.core.Element} this
12364      * @member Ext.core.Element
12365      * @method un
12366      */
12367     ep.un = ep.removeListener;
12368
12369     /**
12370      * Removes all previous added listeners from this element
12371      * @return {Ext.core.Element} this
12372      * @member Ext.core.Element
12373      * @method clearListeners
12374      */
12375     ep.clearListeners = ep.removeAllListeners;
12376
12377     /**
12378      * Removes this element's dom reference.  Note that event and cache removal is handled at {@link Ext#removeNode Ext.removeNode}.
12379      * Alias to {@link #remove}.
12380      * @member Ext.core.Element
12381      * @method destroy
12382      */
12383     ep.destroy = ep.remove;
12384
12385     /**
12386      * true to automatically adjust width and height settings for box-model issues (default to true)
12387      */
12388     ep.autoBoxAdjust = true;
12389
12390     // private
12391     var unitPattern = /\d+(px|em|%|en|ex|pt|in|cm|mm|pc)$/i,
12392     docEl;
12393
12394     /**
12395      * Retrieves Ext.core.Element objects.
12396      * <p><b>This method does not retrieve {@link Ext.Component Component}s.</b> This method
12397      * retrieves Ext.core.Element objects which encapsulate DOM elements. To retrieve a Component by
12398      * its ID, use {@link Ext.ComponentManager#get}.</p>
12399      * <p>Uses simple caching to consistently return the same object. Automatically fixes if an
12400      * object was recreated with the same id via AJAX or DOM.</p>
12401      * @param {Mixed} el The id of the node, a DOM Node or an existing Element.
12402      * @return {Element} The Element object (or null if no matching element was found)
12403      * @static
12404      * @member Ext.core.Element
12405      * @method get
12406      */
12407     El.get = function(el) {
12408         var ex,
12409         elm,
12410         id;
12411         if (!el) {
12412             return null;
12413         }
12414         if (typeof el == "string") {
12415             // element id
12416             if (! (elm = DOC.getElementById(el))) {
12417                 return null;
12418             }
12419             if (EC[el] && EC[el].el) {
12420                 ex = EC[el].el;
12421                 ex.dom = elm;
12422             } else {
12423                 ex = El.addToCache(new El(elm));
12424             }
12425             return ex;
12426         } else if (el.tagName) {
12427             // dom element
12428             if (! (id = el.id)) {
12429                 id = Ext.id(el);
12430             }
12431             if (EC[id] && EC[id].el) {
12432                 ex = EC[id].el;
12433                 ex.dom = el;
12434             } else {
12435                 ex = El.addToCache(new El(el));
12436             }
12437             return ex;
12438         } else if (el instanceof El) {
12439             if (el != docEl) {
12440                 // refresh dom element in case no longer valid,
12441                 // catch case where it hasn't been appended
12442                 // If an el instance is passed, don't pass to getElementById without some kind of id
12443                 if (Ext.isIE && (el.id == undefined || el.id == '')) {
12444                     el.dom = el.dom;
12445                 } else {
12446                     el.dom = DOC.getElementById(el.id) || el.dom;
12447                 }
12448             }
12449             return el;
12450         } else if (el.isComposite) {
12451             return el;
12452         } else if (Ext.isArray(el)) {
12453             return El.select(el);
12454         } else if (el == DOC) {
12455             // create a bogus element object representing the document object
12456             if (!docEl) {
12457                 var f = function() {};
12458                 f.prototype = El.prototype;
12459                 docEl = new f();
12460                 docEl.dom = DOC;
12461             }
12462             return docEl;
12463         }
12464         return null;
12465     };
12466
12467     El.addToCache = function(el, id) {
12468         if (el) {
12469             id = id || el.id;
12470             EC[id] = {
12471                 el: el,
12472                 data: {},
12473                 events: {}
12474             };
12475         }
12476         return el;
12477     };
12478
12479     // private method for getting and setting element data
12480     El.data = function(el, key, value) {
12481         el = El.get(el);
12482         if (!el) {
12483             return null;
12484         }
12485         var c = EC[el.id].data;
12486         if (arguments.length == 2) {
12487             return c[key];
12488         } else {
12489             return (c[key] = value);
12490         }
12491     };
12492
12493     // private
12494     // Garbage collection - uncache elements/purge listeners on orphaned elements
12495     // so we don't hold a reference and cause the browser to retain them
12496     function garbageCollect() {
12497         if (!Ext.enableGarbageCollector) {
12498             clearInterval(El.collectorThreadId);
12499         } else {
12500             var eid,
12501             el,
12502             d,
12503             o;
12504
12505             for (eid in EC) {
12506                 if (!EC.hasOwnProperty(eid)) {
12507                     continue;
12508                 }
12509                 o = EC[eid];
12510                 if (o.skipGarbageCollection) {
12511                     continue;
12512                 }
12513                 el = o.el;
12514                 d = el.dom;
12515                 // -------------------------------------------------------
12516                 // Determining what is garbage:
12517                 // -------------------------------------------------------
12518                 // !d
12519                 // dom node is null, definitely garbage
12520                 // -------------------------------------------------------
12521                 // !d.parentNode
12522                 // no parentNode == direct orphan, definitely garbage
12523                 // -------------------------------------------------------
12524                 // !d.offsetParent && !document.getElementById(eid)
12525                 // display none elements have no offsetParent so we will
12526                 // also try to look it up by it's id. However, check
12527                 // offsetParent first so we don't do unneeded lookups.
12528                 // This enables collection of elements that are not orphans
12529                 // directly, but somewhere up the line they have an orphan
12530                 // parent.
12531                 // -------------------------------------------------------
12532                 if (!d || !d.parentNode || (!d.offsetParent && !DOC.getElementById(eid))) {
12533                     if (d && Ext.enableListenerCollection) {
12534                         Ext.EventManager.removeAll(d);
12535                     }
12536                     delete EC[eid];
12537                 }
12538             }
12539             // Cleanup IE Object leaks
12540             if (Ext.isIE) {
12541                 var t = {};
12542                 for (eid in EC) {
12543                     if (!EC.hasOwnProperty(eid)) {
12544                         continue;
12545                     }
12546                     t[eid] = EC[eid];
12547                 }
12548                 EC = Ext.cache = t;
12549             }
12550         }
12551     }
12552     El.collectorThreadId = setInterval(garbageCollect, 30000);
12553
12554     var flyFn = function() {};
12555     flyFn.prototype = El.prototype;
12556
12557     // dom is optional
12558     El.Flyweight = function(dom) {
12559         this.dom = dom;
12560     };
12561
12562     El.Flyweight.prototype = new flyFn();
12563     El.Flyweight.prototype.isFlyweight = true;
12564     El._flyweights = {};
12565
12566     /**
12567      * <p>Gets the globally shared flyweight Element, with the passed node as the active element. Do not store a reference to this element -
12568      * the dom node can be overwritten by other code. Shorthand of {@link Ext.core.Element#fly}</p>
12569      * <p>Use this to make one-time references to DOM elements which are not going to be accessed again either by
12570      * application code, or by Ext's classes. If accessing an element which will be processed regularly, then {@link Ext#get Ext.get}
12571      * will be more appropriate to take advantage of the caching provided by the Ext.core.Element class.</p>
12572      * @param {String/HTMLElement} el The dom node or id
12573      * @param {String} named (optional) Allows for creation of named reusable flyweights to prevent conflicts
12574      * (e.g. internally Ext uses "_global")
12575      * @return {Element} The shared Element object (or null if no matching element was found)
12576      * @member Ext.core.Element
12577      * @method fly
12578      */
12579     El.fly = function(el, named) {
12580         var ret = null;
12581         named = named || '_global';
12582         el = Ext.getDom(el);
12583         if (el) {
12584             (El._flyweights[named] = El._flyweights[named] || new El.Flyweight()).dom = el;
12585             ret = El._flyweights[named];
12586         }
12587         return ret;
12588     };
12589
12590     /**
12591      * Retrieves Ext.core.Element objects.
12592      * <p><b>This method does not retrieve {@link Ext.Component Component}s.</b> This method
12593      * retrieves Ext.core.Element objects which encapsulate DOM elements. To retrieve a Component by
12594      * its ID, use {@link Ext.ComponentManager#get}.</p>
12595      * <p>Uses simple caching to consistently return the same object. Automatically fixes if an
12596      * object was recreated with the same id via AJAX or DOM.</p>
12597      * Shorthand of {@link Ext.core.Element#get}
12598      * @param {Mixed} el The id of the node, a DOM Node or an existing Element.
12599      * @return {Element} The Element object (or null if no matching element was found)
12600      * @member Ext
12601      * @method get
12602      */
12603     Ext.get = El.get;
12604
12605     /**
12606      * <p>Gets the globally shared flyweight Element, with the passed node as the active element. Do not store a reference to this element -
12607      * the dom node can be overwritten by other code. Shorthand of {@link Ext.core.Element#fly}</p>
12608      * <p>Use this to make one-time references to DOM elements which are not going to be accessed again either by
12609      * application code, or by Ext's classes. If accessing an element which will be processed regularly, then {@link Ext#get Ext.get}
12610      * will be more appropriate to take advantage of the caching provided by the Ext.core.Element class.</p>
12611      * @param {String/HTMLElement} el The dom node or id
12612      * @param {String} named (optional) Allows for creation of named reusable flyweights to prevent conflicts
12613      * (e.g. internally Ext uses "_global")
12614      * @return {Element} The shared Element object (or null if no matching element was found)
12615      * @member Ext
12616      * @method fly
12617      */
12618     Ext.fly = El.fly;
12619
12620     // speedy lookup for elements never to box adjust
12621     var noBoxAdjust = Ext.isStrict ? {
12622         select: 1
12623     }: {
12624         input: 1,
12625         select: 1,
12626         textarea: 1
12627     };
12628     if (Ext.isIE || Ext.isGecko) {
12629         noBoxAdjust['button'] = 1;
12630     }
12631 })();
12632
12633 /**
12634  * @class Ext.core.Element
12635  */
12636 Ext.core.Element.addMethods({
12637     /**
12638      * 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)
12639      * @param {String} selector The simple selector to test
12640      * @param {Number/Mixed} maxDepth (optional) The max depth to search as a number or element (defaults to 50 || document.body)
12641      * @param {Boolean} returnEl (optional) True to return a Ext.core.Element object instead of DOM node
12642      * @return {HTMLElement} The matching DOM node (or null if no match was found)
12643      */
12644     findParent : function(simpleSelector, maxDepth, returnEl) {
12645         var p = this.dom,
12646             b = document.body,
12647             depth = 0,
12648             stopEl;
12649
12650         maxDepth = maxDepth || 50;
12651         if (isNaN(maxDepth)) {
12652             stopEl = Ext.getDom(maxDepth);
12653             maxDepth = Number.MAX_VALUE;
12654         }
12655         while (p && p.nodeType == 1 && depth < maxDepth && p != b && p != stopEl) {
12656             if (Ext.DomQuery.is(p, simpleSelector)) {
12657                 return returnEl ? Ext.get(p) : p;
12658             }
12659             depth++;
12660             p = p.parentNode;
12661         }
12662         return null;
12663     },
12664     
12665     /**
12666      * Looks at parent nodes for a match of the passed simple selector (e.g. div.some-class or span:first-child)
12667      * @param {String} selector The simple selector to test
12668      * @param {Number/Mixed} maxDepth (optional) The max depth to
12669             search as a number or element (defaults to 10 || document.body)
12670      * @param {Boolean} returnEl (optional) True to return a Ext.core.Element object instead of DOM node
12671      * @return {HTMLElement} The matching DOM node (or null if no match was found)
12672      */
12673     findParentNode : function(simpleSelector, maxDepth, returnEl) {
12674         var p = Ext.fly(this.dom.parentNode, '_internal');
12675         return p ? p.findParent(simpleSelector, maxDepth, returnEl) : null;
12676     },
12677
12678     /**
12679      * Walks up the dom looking for a parent node that matches the passed simple selector (e.g. div.some-class or span:first-child).
12680      * This is a shortcut for findParentNode() that always returns an Ext.core.Element.
12681      * @param {String} selector The simple selector to test
12682      * @param {Number/Mixed} maxDepth (optional) The max depth to
12683             search as a number or element (defaults to 10 || document.body)
12684      * @return {Ext.core.Element} The matching DOM node (or null if no match was found)
12685      */
12686     up : function(simpleSelector, maxDepth) {
12687         return this.findParentNode(simpleSelector, maxDepth, true);
12688     },
12689
12690     /**
12691      * Creates a {@link Ext.CompositeElement} for child nodes based on the passed CSS selector (the selector should not contain an id).
12692      * @param {String} selector The CSS selector
12693      * @return {CompositeElement/CompositeElement} The composite element
12694      */
12695     select : function(selector) {
12696         return Ext.core.Element.select(selector, false,  this.dom);
12697     },
12698
12699     /**
12700      * Selects child nodes based on the passed CSS selector (the selector should not contain an id).
12701      * @param {String} selector The CSS selector
12702      * @return {Array} An array of the matched nodes
12703      */
12704     query : function(selector) {
12705         return Ext.DomQuery.select(selector, this.dom);
12706     },
12707
12708     /**
12709      * Selects a single child at any depth below this element based on the passed CSS selector (the selector should not contain an id).
12710      * @param {String} selector The CSS selector
12711      * @param {Boolean} returnDom (optional) True to return the DOM node instead of Ext.core.Element (defaults to false)
12712      * @return {HTMLElement/Ext.core.Element} The child Ext.core.Element (or DOM node if returnDom = true)
12713      */
12714     down : function(selector, returnDom) {
12715         var n = Ext.DomQuery.selectNode(selector, this.dom);
12716         return returnDom ? n : Ext.get(n);
12717     },
12718
12719     /**
12720      * Selects a single *direct* child based on the passed CSS selector (the selector should not contain an id).
12721      * @param {String} selector The CSS selector
12722      * @param {Boolean} returnDom (optional) True to return the DOM node instead of Ext.core.Element (defaults to false)
12723      * @return {HTMLElement/Ext.core.Element} The child Ext.core.Element (or DOM node if returnDom = true)
12724      */
12725     child : function(selector, returnDom) {
12726         var node,
12727             me = this,
12728             id;
12729         id = Ext.get(me).id;
12730         // Escape . or :
12731         id = id.replace(/[\.:]/g, "\\$0");
12732         node = Ext.DomQuery.selectNode('#' + id + " > " + selector, me.dom);
12733         return returnDom ? node : Ext.get(node);
12734     },
12735
12736      /**
12737      * Gets the parent node for this element, optionally chaining up trying to match a selector
12738      * @param {String} selector (optional) Find a parent node that matches the passed simple selector
12739      * @param {Boolean} returnDom (optional) True to return a raw dom node instead of an Ext.core.Element
12740      * @return {Ext.core.Element/HTMLElement} The parent node or null
12741      */
12742     parent : function(selector, returnDom) {
12743         return this.matchNode('parentNode', 'parentNode', selector, returnDom);
12744     },
12745
12746      /**
12747      * Gets the next sibling, skipping text nodes
12748      * @param {String} selector (optional) Find the next sibling that matches the passed simple selector
12749      * @param {Boolean} returnDom (optional) True to return a raw dom node instead of an Ext.core.Element
12750      * @return {Ext.core.Element/HTMLElement} The next sibling or null
12751      */
12752     next : function(selector, returnDom) {
12753         return this.matchNode('nextSibling', 'nextSibling', selector, returnDom);
12754     },
12755
12756     /**
12757      * Gets the previous sibling, skipping text nodes
12758      * @param {String} selector (optional) Find the previous sibling that matches the passed simple selector
12759      * @param {Boolean} returnDom (optional) True to return a raw dom node instead of an Ext.core.Element
12760      * @return {Ext.core.Element/HTMLElement} The previous sibling or null
12761      */
12762     prev : function(selector, returnDom) {
12763         return this.matchNode('previousSibling', 'previousSibling', selector, returnDom);
12764     },
12765
12766
12767     /**
12768      * Gets the first child, skipping text nodes
12769      * @param {String} selector (optional) Find the next sibling that matches the passed simple selector
12770      * @param {Boolean} returnDom (optional) True to return a raw dom node instead of an Ext.core.Element
12771      * @return {Ext.core.Element/HTMLElement} The first child or null
12772      */
12773     first : function(selector, returnDom) {
12774         return this.matchNode('nextSibling', 'firstChild', selector, returnDom);
12775     },
12776
12777     /**
12778      * Gets the last child, skipping text nodes
12779      * @param {String} selector (optional) Find the previous sibling that matches the passed simple selector
12780      * @param {Boolean} returnDom (optional) True to return a raw dom node instead of an Ext.core.Element
12781      * @return {Ext.core.Element/HTMLElement} The last child or null
12782      */
12783     last : function(selector, returnDom) {
12784         return this.matchNode('previousSibling', 'lastChild', selector, returnDom);
12785     },
12786
12787     matchNode : function(dir, start, selector, returnDom) {
12788         if (!this.dom) {
12789             return null;
12790         }
12791         
12792         var n = this.dom[start];
12793         while (n) {
12794             if (n.nodeType == 1 && (!selector || Ext.DomQuery.is(n, selector))) {
12795                 return !returnDom ? Ext.get(n) : n;
12796             }
12797             n = n[dir];
12798         }
12799         return null;
12800     }
12801 });
12802
12803 /**
12804  * @class Ext.core.Element
12805  */
12806 Ext.core.Element.addMethods({
12807     /**
12808      * Appends the passed element(s) to this element
12809      * @param {String/HTMLElement/Array/Element/CompositeElement} el
12810      * @return {Ext.core.Element} this
12811      */
12812     appendChild : function(el) {
12813         return Ext.get(el).appendTo(this);
12814     },
12815
12816     /**
12817      * Appends this element to the passed element
12818      * @param {Mixed} el The new parent element
12819      * @return {Ext.core.Element} this
12820      */
12821     appendTo : function(el) {
12822         Ext.getDom(el).appendChild(this.dom);
12823         return this;
12824     },
12825
12826     /**
12827      * Inserts this element before the passed element in the DOM
12828      * @param {Mixed} el The element before which this element will be inserted
12829      * @return {Ext.core.Element} this
12830      */
12831     insertBefore : function(el) {
12832         el = Ext.getDom(el);
12833         el.parentNode.insertBefore(this.dom, el);
12834         return this;
12835     },
12836
12837     /**
12838      * Inserts this element after the passed element in the DOM
12839      * @param {Mixed} el The element to insert after
12840      * @return {Ext.core.Element} this
12841      */
12842     insertAfter : function(el) {
12843         el = Ext.getDom(el);
12844         el.parentNode.insertBefore(this.dom, el.nextSibling);
12845         return this;
12846     },
12847
12848     /**
12849      * Inserts (or creates) an element (or DomHelper config) as the first child of this element
12850      * @param {Mixed/Object} el The id or element to insert or a DomHelper config to create and insert
12851      * @return {Ext.core.Element} The new child
12852      */
12853     insertFirst : function(el, returnDom) {
12854         el = el || {};
12855         if (el.nodeType || el.dom || typeof el == 'string') { // element
12856             el = Ext.getDom(el);
12857             this.dom.insertBefore(el, this.dom.firstChild);
12858             return !returnDom ? Ext.get(el) : el;
12859         }
12860         else { // dh config
12861             return this.createChild(el, this.dom.firstChild, returnDom);
12862         }
12863     },
12864
12865     /**
12866      * Inserts (or creates) the passed element (or DomHelper config) as a sibling of this element
12867      * @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.
12868      * @param {String} where (optional) 'before' or 'after' defaults to before
12869      * @param {Boolean} returnDom (optional) True to return the .;ll;l,raw DOM element instead of Ext.core.Element
12870      * @return {Ext.core.Element} The inserted Element. If an array is passed, the last inserted element is returned.
12871      */
12872     insertSibling: function(el, where, returnDom){
12873         var me = this, rt,
12874         isAfter = (where || 'before').toLowerCase() == 'after',
12875         insertEl;
12876
12877         if(Ext.isArray(el)){
12878             insertEl = me;
12879             Ext.each(el, function(e) {
12880                 rt = Ext.fly(insertEl, '_internal').insertSibling(e, where, returnDom);
12881                 if(isAfter){
12882                     insertEl = rt;
12883                 }
12884             });
12885             return rt;
12886         }
12887
12888         el = el || {};
12889
12890         if(el.nodeType || el.dom){
12891             rt = me.dom.parentNode.insertBefore(Ext.getDom(el), isAfter ? me.dom.nextSibling : me.dom);
12892             if (!returnDom) {
12893                 rt = Ext.get(rt);
12894             }
12895         }else{
12896             if (isAfter && !me.dom.nextSibling) {
12897                 rt = Ext.core.DomHelper.append(me.dom.parentNode, el, !returnDom);
12898             } else {
12899                 rt = Ext.core.DomHelper[isAfter ? 'insertAfter' : 'insertBefore'](me.dom, el, !returnDom);
12900             }
12901         }
12902         return rt;
12903     },
12904
12905     /**
12906      * Replaces the passed element with this element
12907      * @param {Mixed} el The element to replace
12908      * @return {Ext.core.Element} this
12909      */
12910     replace : function(el) {
12911         el = Ext.get(el);
12912         this.insertBefore(el);
12913         el.remove();
12914         return this;
12915     },
12916     
12917     /**
12918      * Replaces this element with the passed element
12919      * @param {Mixed/Object} el The new element or a DomHelper config of an element to create
12920      * @return {Ext.core.Element} this
12921      */
12922     replaceWith: function(el){
12923         var me = this;
12924             
12925         if(el.nodeType || el.dom || typeof el == 'string'){
12926             el = Ext.get(el);
12927             me.dom.parentNode.insertBefore(el, me.dom);
12928         }else{
12929             el = Ext.core.DomHelper.insertBefore(me.dom, el);
12930         }
12931         
12932         delete Ext.cache[me.id];
12933         Ext.removeNode(me.dom);      
12934         me.id = Ext.id(me.dom = el);
12935         Ext.core.Element.addToCache(me.isFlyweight ? new Ext.core.Element(me.dom) : me);     
12936         return me;
12937     },
12938     
12939     /**
12940      * Creates the passed DomHelper config and appends it to this element or optionally inserts it before the passed child element.
12941      * @param {Object} config DomHelper element config object.  If no tag is specified (e.g., {tag:'input'}) then a div will be
12942      * automatically generated with the specified attributes.
12943      * @param {HTMLElement} insertBefore (optional) a child element of this element
12944      * @param {Boolean} returnDom (optional) true to return the dom node instead of creating an Element
12945      * @return {Ext.core.Element} The new child element
12946      */
12947     createChild : function(config, insertBefore, returnDom) {
12948         config = config || {tag:'div'};
12949         if (insertBefore) {
12950             return Ext.core.DomHelper.insertBefore(insertBefore, config, returnDom !== true);
12951         }
12952         else {
12953             return Ext.core.DomHelper[!this.dom.firstChild ? 'insertFirst' : 'append'](this.dom, config,  returnDom !== true);
12954         }
12955     },
12956
12957     /**
12958      * Creates and wraps this element with another element
12959      * @param {Object} config (optional) DomHelper element config object for the wrapper element or null for an empty div
12960      * @param {Boolean} returnDom (optional) True to return the raw DOM element instead of Ext.core.Element
12961      * @return {HTMLElement/Element} The newly created wrapper element
12962      */
12963     wrap : function(config, returnDom) {
12964         var newEl = Ext.core.DomHelper.insertBefore(this.dom, config || {tag: "div"}, !returnDom),
12965             d = newEl.dom || newEl;
12966
12967         d.appendChild(this.dom);
12968         return newEl;
12969     },
12970
12971     /**
12972      * Inserts an html fragment into this element
12973      * @param {String} where Where to insert the html in relation to this element - beforeBegin, afterBegin, beforeEnd, afterEnd.
12974      * @param {String} html The HTML fragment
12975      * @param {Boolean} returnEl (optional) True to return an Ext.core.Element (defaults to false)
12976      * @return {HTMLElement/Ext.core.Element} The inserted node (or nearest related if more than 1 inserted)
12977      */
12978     insertHtml : function(where, html, returnEl) {
12979         var el = Ext.core.DomHelper.insertHtml(where, this.dom, html);
12980         return returnEl ? Ext.get(el) : el;
12981     }
12982 });
12983
12984 /**
12985  * @class Ext.core.Element
12986  */
12987 (function(){
12988     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>';
12989     // local style camelizing for speed
12990     var supports = Ext.supports,
12991         view = document.defaultView,
12992         opacityRe = /alpha\(opacity=(.*)\)/i,
12993         trimRe = /^\s+|\s+$/g,
12994         spacesRe = /\s+/,
12995         wordsRe = /\w/g,
12996         adjustDirect2DTableRe = /table-row|table-.*-group/,
12997         INTERNAL = '_internal',
12998         PADDING = 'padding',
12999         MARGIN = 'margin',
13000         BORDER = 'border',
13001         LEFT = '-left',
13002         RIGHT = '-right',
13003         TOP = '-top',
13004         BOTTOM = '-bottom',
13005         WIDTH = '-width',
13006         MATH = Math,
13007         HIDDEN = 'hidden',
13008         ISCLIPPED = 'isClipped',
13009         OVERFLOW = 'overflow',
13010         OVERFLOWX = 'overflow-x',
13011         OVERFLOWY = 'overflow-y',
13012         ORIGINALCLIP = 'originalClip',
13013         // special markup used throughout Ext when box wrapping elements
13014         borders = {l: BORDER + LEFT + WIDTH, r: BORDER + RIGHT + WIDTH, t: BORDER + TOP + WIDTH, b: BORDER + BOTTOM + WIDTH},
13015         paddings = {l: PADDING + LEFT, r: PADDING + RIGHT, t: PADDING + TOP, b: PADDING + BOTTOM},
13016         margins = {l: MARGIN + LEFT, r: MARGIN + RIGHT, t: MARGIN + TOP, b: MARGIN + BOTTOM},
13017         data = Ext.core.Element.data;
13018
13019     Ext.override(Ext.core.Element, {
13020         
13021         /**
13022          * TODO: Look at this
13023          */
13024         // private  ==> used by Fx
13025         adjustWidth : function(width) {
13026             var me = this,
13027                 isNum = (typeof width == 'number');
13028                 
13029             if(isNum && me.autoBoxAdjust && !me.isBorderBox()){
13030                width -= (me.getBorderWidth("lr") + me.getPadding("lr"));
13031             }
13032             return (isNum && width < 0) ? 0 : width;
13033         },
13034
13035         // private   ==> used by Fx
13036         adjustHeight : function(height) {
13037             var me = this,
13038                 isNum = (typeof height == "number");
13039                 
13040             if(isNum && me.autoBoxAdjust && !me.isBorderBox()){
13041                height -= (me.getBorderWidth("tb") + me.getPadding("tb"));
13042             }
13043             return (isNum && height < 0) ? 0 : height;
13044         },
13045
13046
13047         /**
13048          * Adds one or more CSS classes to the element. Duplicate classes are automatically filtered out.
13049          * @param {String/Array} className The CSS classes to add separated by space, or an array of classes
13050          * @return {Ext.core.Element} this
13051          */
13052         addCls : function(className){
13053             var me = this,
13054                 cls = [],
13055                 space = ((me.dom.className.replace(trimRe, '') == '') ? "" : " "),
13056                 i, len, v;
13057             if (!Ext.isDefined(className)) {
13058                 return me;
13059             }
13060             // Separate case is for speed
13061             if (!Ext.isArray(className)) {
13062                 if (typeof className === 'string') {
13063                     className = className.replace(trimRe, '').split(spacesRe);
13064                     if (className.length === 1) {
13065                         className = className[0];
13066                         if (!me.hasCls(className)) {
13067                             me.dom.className += space + className;
13068                         }
13069                     } else {
13070                         this.addCls(className);
13071                     }
13072                 }
13073             } else {
13074                 for (i = 0, len = className.length; i < len; i++) {
13075                     v = className[i];
13076                     if (typeof v == 'string' && (' ' + me.dom.className + ' ').indexOf(' ' + v + ' ') == -1) {
13077                         cls.push(v);
13078                     }
13079                 }
13080                 if (cls.length) {
13081                     me.dom.className += space + cls.join(" ");
13082                 }
13083             }
13084             return me;
13085         },
13086
13087         /**
13088          * Removes one or more CSS classes from the element.
13089          * @param {String/Array} className The CSS classes to remove separated by space, or an array of classes
13090          * @return {Ext.core.Element} this
13091          */
13092         removeCls : function(className){
13093             var me = this,
13094                 i, idx, len, cls, elClasses;
13095             if (!Ext.isDefined(className)) {
13096                 return me;
13097             }
13098             if (!Ext.isArray(className)){
13099                 className = className.replace(trimRe, '').split(spacesRe);
13100             }
13101             if (me.dom && me.dom.className) {
13102                 elClasses = me.dom.className.replace(trimRe, '').split(spacesRe);
13103                 for (i = 0, len = className.length; i < len; i++) {
13104                     cls = className[i];
13105                     if (typeof cls == 'string') {
13106                         cls = cls.replace(trimRe, '');
13107                         idx = Ext.Array.indexOf(elClasses, cls);
13108                         if (idx != -1) {
13109                             elClasses.splice(idx, 1);
13110                         }
13111                     }
13112                 }
13113                 me.dom.className = elClasses.join(" ");
13114             }
13115             return me;
13116         },
13117
13118         /**
13119          * Adds one or more CSS classes to this element and removes the same class(es) from all siblings.
13120          * @param {String/Array} className The CSS class to add, or an array of classes
13121          * @return {Ext.core.Element} this
13122          */
13123         radioCls : function(className){
13124             var cn = this.dom.parentNode.childNodes,
13125                 v, i, len;
13126             className = Ext.isArray(className) ? className : [className];
13127             for (i = 0, len = cn.length; i < len; i++) {
13128                 v = cn[i];
13129                 if (v && v.nodeType == 1) {
13130                     Ext.fly(v, '_internal').removeCls(className);
13131                 }
13132             }
13133             return this.addCls(className);
13134         },
13135
13136         /**
13137          * Toggles the specified CSS class on this element (removes it if it already exists, otherwise adds it).
13138          * @param {String} className The CSS class to toggle
13139          * @return {Ext.core.Element} this
13140          */
13141         toggleCls : Ext.supports.ClassList ?
13142             function(className) {
13143                 this.dom.classList.toggle(Ext.String.trim(className));
13144                 return this;
13145             } :
13146             function(className) {
13147                 return this.hasCls(className) ? this.removeCls(className) : this.addCls(className);
13148             },
13149
13150         /**
13151          * Checks if the specified CSS class exists on this element's DOM node.
13152          * @param {String} className The CSS class to check for
13153          * @return {Boolean} True if the class exists, else false
13154          */
13155         hasCls : Ext.supports.ClassList ?
13156             function(className) {
13157                 if (!className) {
13158                     return false;
13159                 }
13160                 className = className.split(spacesRe);
13161                 var ln = className.length,
13162                     i = 0;
13163                 for (; i < ln; i++) {
13164                     if (className[i] && this.dom.classList.contains(className[i])) {
13165                         return true;
13166                     }
13167                 }
13168                 return false;
13169             } :
13170             function(className){
13171                 return className && (' ' + this.dom.className + ' ').indexOf(' ' + className + ' ') != -1;
13172             },
13173
13174         /**
13175          * Replaces a CSS class on the element with another.  If the old name does not exist, the new name will simply be added.
13176          * @param {String} oldClassName The CSS class to replace
13177          * @param {String} newClassName The replacement CSS class
13178          * @return {Ext.core.Element} this
13179          */
13180         replaceCls : function(oldClassName, newClassName){
13181             return this.removeCls(oldClassName).addCls(newClassName);
13182         },
13183
13184         isStyle : function(style, val) {
13185             return this.getStyle(style) == val;
13186         },
13187
13188         /**
13189          * Normalizes currentStyle and computedStyle.
13190          * @param {String} property The style property whose value is returned.
13191          * @return {String} The current value of the style property for this element.
13192          */
13193         getStyle : function(){
13194             return view && view.getComputedStyle ?
13195                 function(prop){
13196                     var el = this.dom,
13197                         v, cs, out, display;
13198
13199                     if(el == document){
13200                         return null;
13201                     }
13202                     prop = Ext.core.Element.normalize(prop);
13203                     out = (v = el.style[prop]) ? v :
13204                            (cs = view.getComputedStyle(el, "")) ? cs[prop] : null;
13205                            
13206                     // Ignore cases when the margin is correctly reported as 0, the bug only shows
13207                     // numbers larger.
13208                     if(prop == 'marginRight' && out != '0px' && !supports.RightMargin){
13209                         display = this.getStyle('display');
13210                         el.style.display = 'inline-block';
13211                         out = view.getComputedStyle(el, '').marginRight;
13212                         el.style.display = display;
13213                     }
13214                     
13215                     if(prop == 'backgroundColor' && out == 'rgba(0, 0, 0, 0)' && !supports.TransparentColor){
13216                         out = 'transparent';
13217                     }
13218                     return out;
13219                 } :
13220                 function(prop){
13221                     var el = this.dom,
13222                         m, cs;
13223
13224                     if (el == document) {
13225                         return null;
13226                     }
13227                     
13228                     if (prop == 'opacity') {
13229                         if (el.style.filter.match) {
13230                             m = el.style.filter.match(opacityRe);
13231                             if(m){
13232                                 var fv = parseFloat(m[1]);
13233                                 if(!isNaN(fv)){
13234                                     return fv ? fv / 100 : 0;
13235                                 }
13236                             }
13237                         }
13238                         return 1;
13239                     }
13240                     prop = Ext.core.Element.normalize(prop);
13241                     return el.style[prop] || ((cs = el.currentStyle) ? cs[prop] : null);
13242                 };
13243         }(),
13244
13245         /**
13246          * Return the CSS color for the specified CSS attribute. rgb, 3 digit (like #fff) and valid values
13247          * are convert to standard 6 digit hex color.
13248          * @param {String} attr The css attribute
13249          * @param {String} defaultValue The default value to use when a valid color isn't found
13250          * @param {String} prefix (optional) defaults to #. Use an empty string when working with
13251          * color anims.
13252          */
13253         getColor : function(attr, defaultValue, prefix){
13254             var v = this.getStyle(attr),
13255                 color = prefix || prefix === '' ? prefix : '#',
13256                 h;
13257
13258             if(!v || (/transparent|inherit/.test(v))) {
13259                 return defaultValue;
13260             }
13261             if(/^r/.test(v)){
13262                 Ext.each(v.slice(4, v.length -1).split(','), function(s){
13263                     h = parseInt(s, 10);
13264                     color += (h < 16 ? '0' : '') + h.toString(16);
13265                 });
13266             }else{
13267                 v = v.replace('#', '');
13268                 color += v.length == 3 ? v.replace(/^(\w)(\w)(\w)$/, '$1$1$2$2$3$3') : v;
13269             }
13270             return(color.length > 5 ? color.toLowerCase() : defaultValue);
13271         },
13272
13273         /**
13274          * Wrapper for setting style properties, also takes single object parameter of multiple styles.
13275          * @param {String/Object} property The style property to be set, or an object of multiple styles.
13276          * @param {String} value (optional) The value to apply to the given property, or null if an object was passed.
13277          * @return {Ext.core.Element} this
13278          */
13279         setStyle : function(prop, value){
13280             var me = this,
13281                 tmp, style;
13282
13283             if (!me.dom) {
13284                 return me;
13285             }
13286
13287             if (!Ext.isObject(prop)) {
13288                 tmp = {};
13289                 tmp[prop] = value;
13290                 prop = tmp;
13291             }
13292             for (style in prop) {
13293                 if (prop.hasOwnProperty(style)) {
13294                     value = Ext.value(prop[style], '');
13295                     if (style == 'opacity') {
13296                         me.setOpacity(value);
13297                     }
13298                     else {
13299                         me.dom.style[Ext.core.Element.normalize(style)] = value;
13300                     }
13301                 }
13302             }
13303             return me;
13304         },
13305
13306         /**
13307          * Set the opacity of the element
13308          * @param {Float} opacity The new opacity. 0 = transparent, .5 = 50% visibile, 1 = fully visible, etc
13309          * @param {Boolean/Object} animate (optional) a standard Element animation config object or <tt>true</tt> for
13310          * the default animation (<tt>{duration: .35, easing: 'easeIn'}</tt>)
13311          * @return {Ext.core.Element} this
13312          */
13313         setOpacity: function(opacity, animate) {
13314             var me = this,
13315                 dom = me.dom,
13316                 val,
13317                 style;
13318
13319             if (!me.dom) {
13320                 return me;
13321             }
13322
13323             style = me.dom.style;
13324
13325             if (!animate || !me.anim) {
13326                 if (!Ext.supports.Opacity) {
13327                     opacity = opacity < 1 ? 'alpha(opacity=' + opacity * 100 + ')': '';
13328                     val = style.filter.replace(opacityRe, '').replace(trimRe, '');
13329
13330                     style.zoom = 1;
13331                     style.filter = val + (val.length > 0 ? ' ': '') + opacity;
13332                 }
13333                 else {
13334                     style.opacity = opacity;
13335                 }
13336             }
13337             else {
13338                 if (!Ext.isObject(animate)) {
13339                     animate = {
13340                         duration: 350,
13341                         easing: 'ease-in'
13342                     };
13343                 }
13344                 me.animate(Ext.applyIf({
13345                     to: {
13346                         opacity: opacity
13347                     }
13348                 },
13349                 animate));
13350             }
13351             return me;
13352         },
13353
13354
13355         /**
13356          * Clears any opacity settings from this element. Required in some cases for IE.
13357          * @return {Ext.core.Element} this
13358          */
13359         clearOpacity : function(){
13360             var style = this.dom.style;
13361             if(!Ext.supports.Opacity){
13362                 if(!Ext.isEmpty(style.filter)){
13363                     style.filter = style.filter.replace(opacityRe, '').replace(trimRe, '');
13364                 }
13365             }else{
13366                 style.opacity = style['-moz-opacity'] = style['-khtml-opacity'] = '';
13367             }
13368             return this;
13369         },
13370         
13371         /**
13372          * @private
13373          * Returns 1 if the browser returns the subpixel dimension rounded to the lowest pixel.
13374          * @return {Number} 0 or 1 
13375          */
13376         adjustDirect2DDimension: function(dimension) {
13377             var me = this,
13378                 dom = me.dom,
13379                 display = me.getStyle('display'),
13380                 inlineDisplay = dom.style['display'],
13381                 inlinePosition = dom.style['position'],
13382                 originIndex = dimension === 'width' ? 0 : 1,
13383                 floating;
13384                 
13385             if (display === 'inline') {
13386                 dom.style['display'] = 'inline-block';
13387             }
13388
13389             dom.style['position'] = display.match(adjustDirect2DTableRe) ? 'absolute' : 'static';
13390
13391             // floating will contain digits that appears after the decimal point
13392             // if height or width are set to auto we fallback to msTransformOrigin calculation
13393             floating = (parseFloat(me.getStyle(dimension)) || parseFloat(dom.currentStyle.msTransformOrigin.split(' ')[originIndex]) * 2) % 1;
13394             
13395             dom.style['position'] = inlinePosition;
13396             
13397             if (display === 'inline') {
13398                 dom.style['display'] = inlineDisplay;
13399             }
13400
13401             return floating;
13402         },
13403         
13404         /**
13405          * Returns the offset height of the element
13406          * @param {Boolean} contentHeight (optional) true to get the height minus borders and padding
13407          * @return {Number} The element's height
13408          */
13409         getHeight: function(contentHeight, preciseHeight) {
13410             var me = this,
13411                 dom = me.dom,
13412                 hidden = Ext.isIE && me.isStyle('display', 'none'),
13413                 height, overflow, style, floating;
13414
13415             // IE Quirks mode acts more like a max-size measurement unless overflow is hidden during measurement.
13416             // We will put the overflow back to it's original value when we are done measuring.
13417             if (Ext.isIEQuirks) {
13418                 style = dom.style;
13419                 overflow = style.overflow;
13420                 me.setStyle({ overflow: 'hidden'});
13421             }
13422
13423             height = dom.offsetHeight;
13424
13425             height = MATH.max(height, hidden ? 0 : dom.clientHeight) || 0;
13426
13427             // IE9 Direct2D dimension rounding bug
13428             if (!hidden && Ext.supports.Direct2DBug) {
13429                 floating = me.adjustDirect2DDimension('height');
13430                 if (preciseHeight) {
13431                     height += floating;
13432                 }
13433                 else if (floating > 0 && floating < 0.5) {
13434                     height++;
13435                 }
13436             }
13437
13438             if (contentHeight) {
13439                 height -= (me.getBorderWidth("tb") + me.getPadding("tb"));
13440             }
13441
13442             if (Ext.isIEQuirks) {
13443                 me.setStyle({ overflow: overflow});
13444             }
13445
13446             if (height < 0) {
13447                 height = 0;
13448             }
13449             return height;
13450         },
13451                 
13452         /**
13453          * Returns the offset width of the element
13454          * @param {Boolean} contentWidth (optional) true to get the width minus borders and padding
13455          * @return {Number} The element's width
13456          */
13457         getWidth: function(contentWidth, preciseWidth) {
13458             var me = this,
13459                 dom = me.dom,
13460                 hidden = Ext.isIE && me.isStyle('display', 'none'),
13461                 rect, width, overflow, style, floating, parentPosition;
13462
13463             // IE Quirks mode acts more like a max-size measurement unless overflow is hidden during measurement.
13464             // We will put the overflow back to it's original value when we are done measuring.
13465             if (Ext.isIEQuirks) {
13466                 style = dom.style;
13467                 overflow = style.overflow;
13468                 me.setStyle({overflow: 'hidden'});
13469             }
13470             
13471             // Fix Opera 10.5x width calculation issues 
13472             if (Ext.isOpera10_5) {
13473                 if (dom.parentNode.currentStyle.position === 'relative') {
13474                     parentPosition = dom.parentNode.style.position;
13475                     dom.parentNode.style.position = 'static';
13476                     width = dom.offsetWidth;
13477                     dom.parentNode.style.position = parentPosition;
13478                 }
13479                 width = Math.max(width || 0, dom.offsetWidth);
13480             
13481             // Gecko will in some cases report an offsetWidth that is actually less than the width of the
13482             // text contents, because it measures fonts with sub-pixel precision but rounds the calculated
13483             // value down. Using getBoundingClientRect instead of offsetWidth allows us to get the precise
13484             // subpixel measurements so we can force them to always be rounded up. See
13485             // https://bugzilla.mozilla.org/show_bug.cgi?id=458617
13486             } else if (Ext.supports.BoundingClientRect) {
13487                 rect = dom.getBoundingClientRect();
13488                 width = rect.right - rect.left;
13489                 width = preciseWidth ? width : Math.ceil(width);
13490             } else {
13491                 width = dom.offsetWidth;
13492             }
13493
13494             width = MATH.max(width, hidden ? 0 : dom.clientWidth) || 0;
13495
13496             // IE9 Direct2D dimension rounding bug
13497             if (!hidden && Ext.supports.Direct2DBug) {
13498                 floating = me.adjustDirect2DDimension('width');
13499                 if (preciseWidth) {
13500                     width += floating;
13501                 }
13502                 else if (floating > 0 && floating < 0.5) {
13503                     width++;
13504                 }
13505             }
13506             
13507             if (contentWidth) {
13508                 width -= (me.getBorderWidth("lr") + me.getPadding("lr"));
13509             }
13510             
13511             if (Ext.isIEQuirks) {
13512                 me.setStyle({ overflow: overflow});
13513             }
13514
13515             if (width < 0) {
13516                 width = 0;
13517             }
13518             return width;
13519         },
13520
13521         /**
13522          * Set the width of this Element.
13523          * @param {Mixed} width The new width. This may be one of:<div class="mdetail-params"><ul>
13524          * <li>A Number specifying the new width in this Element's {@link #defaultUnit}s (by default, pixels).</li>
13525          * <li>A String used to set the CSS width style. Animation may <b>not</b> be used.
13526          * </ul></div>
13527          * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
13528          * @return {Ext.core.Element} this
13529          */
13530         setWidth : function(width, animate){
13531             var me = this;
13532             width = me.adjustWidth(width);
13533             if (!animate || !me.anim) {
13534                 me.dom.style.width = me.addUnits(width);
13535             }
13536             else {
13537                 if (!Ext.isObject(animate)) {
13538                     animate = {};
13539                 }
13540                 me.animate(Ext.applyIf({
13541                     to: {
13542                         width: width
13543                     }
13544                 }, animate));
13545             }
13546             return me;
13547         },
13548
13549         /**
13550          * Set the height of this Element.
13551          * <pre><code>
13552 // change the height to 200px and animate with default configuration
13553 Ext.fly('elementId').setHeight(200, true);
13554
13555 // change the height to 150px and animate with a custom configuration
13556 Ext.fly('elId').setHeight(150, {
13557     duration : .5, // animation will have a duration of .5 seconds
13558     // will change the content to "finished"
13559     callback: function(){ this.{@link #update}("finished"); }
13560 });
13561          * </code></pre>
13562          * @param {Mixed} height The new height. This may be one of:<div class="mdetail-params"><ul>
13563          * <li>A Number specifying the new height in this Element's {@link #defaultUnit}s (by default, pixels.)</li>
13564          * <li>A String used to set the CSS height style. Animation may <b>not</b> be used.</li>
13565          * </ul></div>
13566          * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
13567          * @return {Ext.core.Element} this
13568          */
13569          setHeight : function(height, animate){
13570             var me = this;
13571             height = me.adjustHeight(height);
13572             if (!animate || !me.anim) {
13573                 me.dom.style.height = me.addUnits(height);
13574             }
13575             else {
13576                 if (!Ext.isObject(animate)) {
13577                     animate = {};
13578                 }
13579                 me.animate(Ext.applyIf({
13580                     to: {
13581                         height: height
13582                     }
13583                 }, animate));
13584             }
13585             return me;
13586         },
13587
13588         /**
13589          * Gets the width of the border(s) for the specified side(s)
13590          * @param {String} side Can be t, l, r, b or any combination of those to add multiple values. For example,
13591          * passing <tt>'lr'</tt> would get the border <b><u>l</u></b>eft width + the border <b><u>r</u></b>ight width.
13592          * @return {Number} The width of the sides passed added together
13593          */
13594         getBorderWidth : function(side){
13595             return this.addStyles(side, borders);
13596         },
13597
13598         /**
13599          * Gets the width of the padding(s) for the specified side(s)
13600          * @param {String} side Can be t, l, r, b or any combination of those to add multiple values. For example,
13601          * passing <tt>'lr'</tt> would get the padding <b><u>l</u></b>eft + the padding <b><u>r</u></b>ight.
13602          * @return {Number} The padding of the sides passed added together
13603          */
13604         getPadding : function(side){
13605             return this.addStyles(side, paddings);
13606         },
13607
13608         /**
13609          *  Store the current overflow setting and clip overflow on the element - use <tt>{@link #unclip}</tt> to remove
13610          * @return {Ext.core.Element} this
13611          */
13612         clip : function(){
13613             var me = this,
13614                 dom = me.dom;
13615
13616             if(!data(dom, ISCLIPPED)){
13617                 data(dom, ISCLIPPED, true);
13618                 data(dom, ORIGINALCLIP, {
13619                     o: me.getStyle(OVERFLOW),
13620                     x: me.getStyle(OVERFLOWX),
13621                     y: me.getStyle(OVERFLOWY)
13622                 });
13623                 me.setStyle(OVERFLOW, HIDDEN);
13624                 me.setStyle(OVERFLOWX, HIDDEN);
13625                 me.setStyle(OVERFLOWY, HIDDEN);
13626             }
13627             return me;
13628         },
13629
13630         /**
13631          *  Return clipping (overflow) to original clipping before <tt>{@link #clip}</tt> was called
13632          * @return {Ext.core.Element} this
13633          */
13634         unclip : function(){
13635             var me = this,
13636                 dom = me.dom,
13637                 clip;
13638
13639             if(data(dom, ISCLIPPED)){
13640                 data(dom, ISCLIPPED, false);
13641                 clip = data(dom, ORIGINALCLIP);
13642                 if(o.o){
13643                     me.setStyle(OVERFLOW, o.o);
13644                 }
13645                 if(o.x){
13646                     me.setStyle(OVERFLOWX, o.x);
13647                 }
13648                 if(o.y){
13649                     me.setStyle(OVERFLOWY, o.y);
13650                 }
13651             }
13652             return me;
13653         },
13654
13655         // private
13656         addStyles : function(sides, styles){
13657             var totalSize = 0,
13658                 sidesArr = sides.match(wordsRe),
13659                 i = 0,
13660                 len = sidesArr.length,
13661                 side, size;
13662             for (; i < len; i++) {
13663                 side = sidesArr[i];
13664                 size = side && parseInt(this.getStyle(styles[side]), 10);
13665                 if (size) {
13666                     totalSize += MATH.abs(size);
13667                 }
13668             }
13669             return totalSize;
13670         },
13671
13672         margins : margins,
13673         
13674         /**
13675          * More flexible version of {@link #setStyle} for setting style properties.
13676          * @param {String/Object/Function} styles A style specification string, e.g. "width:100px", or object in the form {width:"100px"}, or
13677          * a function which returns such a specification.
13678          * @return {Ext.core.Element} this
13679          */
13680         applyStyles : function(style){
13681             Ext.core.DomHelper.applyStyles(this.dom, style);
13682             return this;
13683         },
13684
13685         /**
13686          * Returns an object with properties matching the styles requested.
13687          * For example, el.getStyles('color', 'font-size', 'width') might return
13688          * {'color': '#FFFFFF', 'font-size': '13px', 'width': '100px'}.
13689          * @param {String} style1 A style name
13690          * @param {String} style2 A style name
13691          * @param {String} etc.
13692          * @return {Object} The style object
13693          */
13694         getStyles : function(){
13695             var styles = {},
13696                 len = arguments.length,
13697                 i = 0, style;
13698                 
13699             for(; i < len; ++i) {
13700                 style = arguments[i];
13701                 styles[style] = this.getStyle(style);
13702             }
13703             return styles;
13704         },
13705
13706        /**
13707         * <p>Wraps the specified element with a special 9 element markup/CSS block that renders by default as
13708         * a gray container with a gradient background, rounded corners and a 4-way shadow.</p>
13709         * <p>This special markup is used throughout Ext when box wrapping elements ({@link Ext.button.Button},
13710         * {@link Ext.panel.Panel} when <tt>{@link Ext.panel.Panel#frame frame=true}</tt>, {@link Ext.window.Window}).  The markup
13711         * is of this form:</p>
13712         * <pre><code>
13713     Ext.core.Element.boxMarkup =
13714     &#39;&lt;div class="{0}-tl">&lt;div class="{0}-tr">&lt;div class="{0}-tc">&lt;/div>&lt;/div>&lt;/div>
13715      &lt;div class="{0}-ml">&lt;div class="{0}-mr">&lt;div class="{0}-mc">&lt;/div>&lt;/div>&lt;/div>
13716      &lt;div class="{0}-bl">&lt;div class="{0}-br">&lt;div class="{0}-bc">&lt;/div>&lt;/div>&lt;/div>&#39;;
13717         * </code></pre>
13718         * <p>Example usage:</p>
13719         * <pre><code>
13720     // Basic box wrap
13721     Ext.get("foo").boxWrap();
13722
13723     // You can also add a custom class and use CSS inheritance rules to customize the box look.
13724     // 'x-box-blue' is a built-in alternative -- look at the related CSS definitions as an example
13725     // for how to create a custom box wrap style.
13726     Ext.get("foo").boxWrap().addCls("x-box-blue");
13727         * </code></pre>
13728         * @param {String} class (optional) A base CSS class to apply to the containing wrapper element
13729         * (defaults to <tt>'x-box'</tt>). Note that there are a number of CSS rules that are dependent on
13730         * this name to make the overall effect work, so if you supply an alternate base class, make sure you
13731         * also supply all of the necessary rules.
13732         * @return {Ext.core.Element} The outermost wrapping element of the created box structure.
13733         */
13734         boxWrap : function(cls){
13735             cls = cls || Ext.baseCSSPrefix + 'box';
13736             var el = Ext.get(this.insertHtml("beforeBegin", "<div class='" + cls + "'>" + Ext.String.format(Ext.core.Element.boxMarkup, cls) + "</div>"));
13737             Ext.DomQuery.selectNode('.' + cls + '-mc', el.dom).appendChild(this.dom);
13738             return el;
13739         },
13740
13741         /**
13742          * Set the size of this Element. If animation is true, both width and height will be animated concurrently.
13743          * @param {Mixed} width The new width. This may be one of:<div class="mdetail-params"><ul>
13744          * <li>A Number specifying the new width in this Element's {@link #defaultUnit}s (by default, pixels).</li>
13745          * <li>A String used to set the CSS width style. Animation may <b>not</b> be used.
13746          * <li>A size object in the format <code>{width: widthValue, height: heightValue}</code>.</li>
13747          * </ul></div>
13748          * @param {Mixed} height The new height. This may be one of:<div class="mdetail-params"><ul>
13749          * <li>A Number specifying the new height in this Element's {@link #defaultUnit}s (by default, pixels).</li>
13750          * <li>A String used to set the CSS height style. Animation may <b>not</b> be used.</li>
13751          * </ul></div>
13752          * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
13753          * @return {Ext.core.Element} this
13754          */
13755         setSize : function(width, height, animate){
13756             var me = this;
13757             if (Ext.isObject(width)){ // in case of object from getSize()
13758                 height = width.height;
13759                 width = width.width;
13760             }
13761             width = me.adjustWidth(width);
13762             height = me.adjustHeight(height);
13763             if(!animate || !me.anim){
13764                 me.dom.style.width = me.addUnits(width);
13765                 me.dom.style.height = me.addUnits(height);
13766             }
13767             else {
13768                 if (!Ext.isObject(animate)) {
13769                     animate = {};
13770                 }
13771                 me.animate(Ext.applyIf({
13772                     to: {
13773                         width: width,
13774                         height: height
13775                     }
13776                 }, animate));
13777             }
13778             return me;
13779         },
13780
13781         /**
13782          * Returns either the offsetHeight or the height of this element based on CSS height adjusted by padding or borders
13783          * when needed to simulate offsetHeight when offsets aren't available. This may not work on display:none elements
13784          * if a height has not been set using CSS.
13785          * @return {Number}
13786          */
13787         getComputedHeight : function(){
13788             var me = this,
13789                 h = Math.max(me.dom.offsetHeight, me.dom.clientHeight);
13790             if(!h){
13791                 h = parseFloat(me.getStyle('height')) || 0;
13792                 if(!me.isBorderBox()){
13793                     h += me.getFrameWidth('tb');
13794                 }
13795             }
13796             return h;
13797         },
13798
13799         /**
13800          * Returns either the offsetWidth or the width of this element based on CSS width adjusted by padding or borders
13801          * when needed to simulate offsetWidth when offsets aren't available. This may not work on display:none elements
13802          * if a width has not been set using CSS.
13803          * @return {Number}
13804          */
13805         getComputedWidth : function(){
13806             var me = this,
13807                 w = Math.max(me.dom.offsetWidth, me.dom.clientWidth);
13808                 
13809             if(!w){
13810                 w = parseFloat(me.getStyle('width')) || 0;
13811                 if(!me.isBorderBox()){
13812                     w += me.getFrameWidth('lr');
13813                 }
13814             }
13815             return w;
13816         },
13817
13818         /**
13819          * Returns the sum width of the padding and borders for the passed "sides". See getBorderWidth()
13820          for more information about the sides.
13821          * @param {String} sides
13822          * @return {Number}
13823          */
13824         getFrameWidth : function(sides, onlyContentBox){
13825             return onlyContentBox && this.isBorderBox() ? 0 : (this.getPadding(sides) + this.getBorderWidth(sides));
13826         },
13827
13828         /**
13829          * Sets up event handlers to add and remove a css class when the mouse is over this element
13830          * @param {String} className
13831          * @return {Ext.core.Element} this
13832          */
13833         addClsOnOver : function(className){
13834             var dom = this.dom;
13835             this.hover(
13836                 function(){
13837                     Ext.fly(dom, INTERNAL).addCls(className);
13838                 },
13839                 function(){
13840                     Ext.fly(dom, INTERNAL).removeCls(className);
13841                 }
13842             );
13843             return this;
13844         },
13845
13846         /**
13847          * Sets up event handlers to add and remove a css class when this element has the focus
13848          * @param {String} className
13849          * @return {Ext.core.Element} this
13850          */
13851         addClsOnFocus : function(className){
13852             var me = this,
13853                 dom = me.dom;
13854             me.on("focus", function(){
13855                 Ext.fly(dom, INTERNAL).addCls(className);
13856             });
13857             me.on("blur", function(){
13858                 Ext.fly(dom, INTERNAL).removeCls(className);
13859             });
13860             return me;
13861         },
13862
13863         /**
13864          * 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)
13865          * @param {String} className
13866          * @return {Ext.core.Element} this
13867          */
13868         addClsOnClick : function(className){
13869             var dom = this.dom;
13870             this.on("mousedown", function(){
13871                 Ext.fly(dom, INTERNAL).addCls(className);
13872                 var d = Ext.getDoc(),
13873                     fn = function(){
13874                         Ext.fly(dom, INTERNAL).removeCls(className);
13875                         d.removeListener("mouseup", fn);
13876                     };
13877                 d.on("mouseup", fn);
13878             });
13879             return this;
13880         },
13881
13882         /**
13883          * <p>Returns the dimensions of the element available to lay content out in.<p>
13884          * <p>If the element (or any ancestor element) has CSS style <code>display : none</code>, the dimensions will be zero.</p>
13885          * example:<pre><code>
13886         var vpSize = Ext.getBody().getViewSize();
13887
13888         // all Windows created afterwards will have a default value of 90% height and 95% width
13889         Ext.Window.override({
13890             width: vpSize.width * 0.9,
13891             height: vpSize.height * 0.95
13892         });
13893         // To handle window resizing you would have to hook onto onWindowResize.
13894         * </code></pre>
13895         *
13896         * getViewSize utilizes clientHeight/clientWidth which excludes sizing of scrollbars.
13897         * To obtain the size including scrollbars, use getStyleSize
13898         *
13899         * Sizing of the document body is handled at the adapter level which handles special cases for IE and strict modes, etc.
13900         */
13901
13902         getViewSize : function(){
13903             var me = this,
13904                 dom = me.dom,
13905                 isDoc = (dom == Ext.getDoc().dom || dom == Ext.getBody().dom),
13906                 style, overflow, ret;
13907
13908             // If the body, use static methods
13909             if (isDoc) {
13910                 ret = {
13911                     width : Ext.core.Element.getViewWidth(),
13912                     height : Ext.core.Element.getViewHeight()
13913                 };
13914
13915             // Else use clientHeight/clientWidth
13916             }
13917             else {
13918                 // IE 6 & IE Quirks mode acts more like a max-size measurement unless overflow is hidden during measurement.
13919                 // We will put the overflow back to it's original value when we are done measuring.
13920                 if (Ext.isIE6 || Ext.isIEQuirks) {
13921                     style = dom.style;
13922                     overflow = style.overflow;
13923                     me.setStyle({ overflow: 'hidden'});
13924                 }
13925                 ret = {
13926                     width : dom.clientWidth,
13927                     height : dom.clientHeight
13928                 };
13929                 if (Ext.isIE6 || Ext.isIEQuirks) {
13930                     me.setStyle({ overflow: overflow });
13931                 }
13932             }
13933             return ret;
13934         },
13935
13936         /**
13937         * <p>Returns the dimensions of the element available to lay content out in.<p>
13938         *
13939         * getStyleSize utilizes prefers style sizing if present, otherwise it chooses the larger of offsetHeight/clientHeight and offsetWidth/clientWidth.
13940         * To obtain the size excluding scrollbars, use getViewSize
13941         *
13942         * Sizing of the document body is handled at the adapter level which handles special cases for IE and strict modes, etc.
13943         */
13944
13945         getStyleSize : function(){
13946             var me = this,
13947                 doc = document,
13948                 d = this.dom,
13949                 isDoc = (d == doc || d == doc.body),
13950                 s = d.style,
13951                 w, h;
13952
13953             // If the body, use static methods
13954             if (isDoc) {
13955                 return {
13956                     width : Ext.core.Element.getViewWidth(),
13957                     height : Ext.core.Element.getViewHeight()
13958                 };
13959             }
13960             // Use Styles if they are set
13961             if(s.width && s.width != 'auto'){
13962                 w = parseFloat(s.width);
13963                 if(me.isBorderBox()){
13964                    w -= me.getFrameWidth('lr');
13965                 }
13966             }
13967             // Use Styles if they are set
13968             if(s.height && s.height != 'auto'){
13969                 h = parseFloat(s.height);
13970                 if(me.isBorderBox()){
13971                    h -= me.getFrameWidth('tb');
13972                 }
13973             }
13974             // Use getWidth/getHeight if style not set.
13975             return {width: w || me.getWidth(true), height: h || me.getHeight(true)};
13976         },
13977
13978         /**
13979          * Returns the size of the element.
13980          * @param {Boolean} contentSize (optional) true to get the width/size minus borders and padding
13981          * @return {Object} An object containing the element's size {width: (element width), height: (element height)}
13982          */
13983         getSize : function(contentSize){
13984             return {width: this.getWidth(contentSize), height: this.getHeight(contentSize)};
13985         },
13986
13987         /**
13988          * Forces the browser to repaint this element
13989          * @return {Ext.core.Element} this
13990          */
13991         repaint : function(){
13992             var dom = this.dom;
13993             this.addCls(Ext.baseCSSPrefix + 'repaint');
13994             setTimeout(function(){
13995                 Ext.fly(dom).removeCls(Ext.baseCSSPrefix + 'repaint');
13996             }, 1);
13997             return this;
13998         },
13999
14000         /**
14001          * Disables text selection for this element (normalized across browsers)
14002          * @return {Ext.core.Element} this
14003          */
14004         unselectable : function(){
14005             var me = this;
14006             me.dom.unselectable = "on";
14007
14008             me.swallowEvent("selectstart", true);
14009             me.applyStyles("-moz-user-select:none;-khtml-user-select:none;");
14010             me.addCls(Ext.baseCSSPrefix + 'unselectable');
14011             
14012             return me;
14013         },
14014
14015         /**
14016          * Returns an object with properties top, left, right and bottom representing the margins of this element unless sides is passed,
14017          * then it returns the calculated width of the sides (see getPadding)
14018          * @param {String} sides (optional) Any combination of l, r, t, b to get the sum of those sides
14019          * @return {Object/Number}
14020          */
14021         getMargin : function(side){
14022             var me = this,
14023                 hash = {t:"top", l:"left", r:"right", b: "bottom"},
14024                 o = {},
14025                 key;
14026
14027             if (!side) {
14028                 for (key in me.margins){
14029                     o[hash[key]] = parseFloat(me.getStyle(me.margins[key])) || 0;
14030                 }
14031                 return o;
14032             } else {
14033                 return me.addStyles.call(me, side, me.margins);
14034             }
14035         }
14036     });
14037 })();
14038 /**
14039  * @class Ext.core.Element
14040  */
14041 /**
14042  * Visibility mode constant for use with {@link #setVisibilityMode}. Use visibility to hide element
14043  * @static
14044  * @type Number
14045  */
14046 Ext.core.Element.VISIBILITY = 1;
14047 /**
14048  * Visibility mode constant for use with {@link #setVisibilityMode}. Use display to hide element
14049  * @static
14050  * @type Number
14051  */
14052 Ext.core.Element.DISPLAY = 2;
14053
14054 /**
14055  * Visibility mode constant for use with {@link #setVisibilityMode}. Use offsets (x and y positioning offscreen)
14056  * to hide element.
14057  * @static
14058  * @type Number
14059  */
14060 Ext.core.Element.OFFSETS = 3;
14061
14062
14063 Ext.core.Element.ASCLASS = 4;
14064
14065 /**
14066  * Defaults to 'x-hide-nosize'
14067  * @static
14068  * @type String
14069  */
14070 Ext.core.Element.visibilityCls = Ext.baseCSSPrefix + 'hide-nosize';
14071
14072 Ext.core.Element.addMethods(function(){
14073     var El = Ext.core.Element,
14074         OPACITY = "opacity",
14075         VISIBILITY = "visibility",
14076         DISPLAY = "display",
14077         HIDDEN = "hidden",
14078         OFFSETS = "offsets",
14079         ASCLASS = "asclass",
14080         NONE = "none",
14081         NOSIZE = 'nosize',
14082         ORIGINALDISPLAY = 'originalDisplay',
14083         VISMODE = 'visibilityMode',
14084         ISVISIBLE = 'isVisible',
14085         data = El.data,
14086         getDisplay = function(dom){
14087             var d = data(dom, ORIGINALDISPLAY);
14088             if(d === undefined){
14089                 data(dom, ORIGINALDISPLAY, d = '');
14090             }
14091             return d;
14092         },
14093         getVisMode = function(dom){
14094             var m = data(dom, VISMODE);
14095             if(m === undefined){
14096                 data(dom, VISMODE, m = 1);
14097             }
14098             return m;
14099         };
14100
14101     return {
14102         /**
14103          * The element's default display mode  (defaults to "")
14104          * @type String
14105          */
14106         originalDisplay : "",
14107         visibilityMode : 1,
14108
14109         /**
14110          * Sets the element's visibility mode. When setVisible() is called it
14111          * will use this to determine whether to set the visibility or the display property.
14112          * @param {Number} visMode Ext.core.Element.VISIBILITY or Ext.core.Element.DISPLAY
14113          * @return {Ext.core.Element} this
14114          */
14115         setVisibilityMode : function(visMode){
14116             data(this.dom, VISMODE, visMode);
14117             return this;
14118         },
14119
14120         /**
14121          * Checks whether the element is currently visible using both visibility and display properties.
14122          * @return {Boolean} True if the element is currently visible, else false
14123          */
14124         isVisible : function() {
14125             var me = this,
14126                 dom = me.dom,
14127                 visible = data(dom, ISVISIBLE);
14128
14129             if(typeof visible == 'boolean'){ //return the cached value if registered
14130                 return visible;
14131             }
14132             //Determine the current state based on display states
14133             visible = !me.isStyle(VISIBILITY, HIDDEN) &&
14134                       !me.isStyle(DISPLAY, NONE) &&
14135                       !((getVisMode(dom) == El.ASCLASS) && me.hasCls(me.visibilityCls || El.visibilityCls));
14136
14137             data(dom, ISVISIBLE, visible);
14138             return visible;
14139         },
14140
14141         /**
14142          * Sets the visibility of the element (see details). If the visibilityMode is set to Element.DISPLAY, it will use
14143          * the display property to hide the element, otherwise it uses visibility. The default is to hide and show using the visibility property.
14144          * @param {Boolean} visible Whether the element is visible
14145          * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
14146          * @return {Ext.core.Element} this
14147          */
14148         setVisible : function(visible, animate){
14149             var me = this, isDisplay, isVisibility, isOffsets, isNosize,
14150                 dom = me.dom,
14151                 visMode = getVisMode(dom);
14152
14153
14154             // hideMode string override
14155             if (typeof animate == 'string'){
14156                 switch (animate) {
14157                     case DISPLAY:
14158                         visMode = El.DISPLAY;
14159                         break;
14160                     case VISIBILITY:
14161                         visMode = El.VISIBILITY;
14162                         break;
14163                     case OFFSETS:
14164                         visMode = El.OFFSETS;
14165                         break;
14166                     case NOSIZE:
14167                     case ASCLASS:
14168                         visMode = El.ASCLASS;
14169                         break;
14170                 }
14171                 me.setVisibilityMode(visMode);
14172                 animate = false;
14173             }
14174
14175             if (!animate || !me.anim) {
14176                 if(visMode == El.ASCLASS ){
14177
14178                     me[visible?'removeCls':'addCls'](me.visibilityCls || El.visibilityCls);
14179
14180                 } else if (visMode == El.DISPLAY){
14181
14182                     return me.setDisplayed(visible);
14183
14184                 } else if (visMode == El.OFFSETS){
14185
14186                     if (!visible){
14187                         // Remember position for restoring, if we are not already hidden by offsets.
14188                         if (!me.hideModeStyles) {
14189                             me.hideModeStyles = {
14190                                 position: me.getStyle('position'),
14191                                 top: me.getStyle('top'),
14192                                 left: me.getStyle('left')
14193                             };
14194                         }
14195                         me.applyStyles({position: 'absolute', top: '-10000px', left: '-10000px'});
14196                     }
14197
14198                     // Only "restore" as position if we have actually been hidden using offsets.
14199                     // Calling setVisible(true) on a positioned element should not reposition it.
14200                     else if (me.hideModeStyles) {
14201                         me.applyStyles(me.hideModeStyles || {position: '', top: '', left: ''});
14202                         delete me.hideModeStyles;
14203                     }
14204
14205                 }else{
14206                     me.fixDisplay();
14207                     // Show by clearing visibility style. Explicitly setting to "visible" overrides parent visibility setting.
14208                     dom.style.visibility = visible ? '' : HIDDEN;
14209                 }
14210             }else{
14211                 // closure for composites
14212                 if(visible){
14213                     me.setOpacity(0.01);
14214                     me.setVisible(true);
14215                 }
14216                 if (!Ext.isObject(animate)) {
14217                     animate = {
14218                         duration: 350,
14219                         easing: 'ease-in'
14220                     };
14221                 }
14222                 me.animate(Ext.applyIf({
14223                     callback: function() {
14224                         visible || me.setVisible(false).setOpacity(1);
14225                     },
14226                     to: {
14227                         opacity: (visible) ? 1 : 0
14228                     }
14229                 }, animate));
14230             }
14231             data(dom, ISVISIBLE, visible);  //set logical visibility state
14232             return me;
14233         },
14234
14235
14236         /**
14237          * @private
14238          * Determine if the Element has a relevant height and width available based
14239          * upon current logical visibility state
14240          */
14241         hasMetrics  : function(){
14242             var dom = this.dom;
14243             return this.isVisible() || (getVisMode(dom) == El.OFFSETS) || (getVisMode(dom) == El.VISIBILITY);
14244         },
14245
14246         /**
14247          * Toggles the element's visibility or display, depending on visibility mode.
14248          * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
14249          * @return {Ext.core.Element} this
14250          */
14251         toggle : function(animate){
14252             var me = this;
14253             me.setVisible(!me.isVisible(), me.anim(animate));
14254             return me;
14255         },
14256
14257         /**
14258          * Sets the CSS display property. Uses originalDisplay if the specified value is a boolean true.
14259          * @param {Mixed} value Boolean value to display the element using its default display, or a string to set the display directly.
14260          * @return {Ext.core.Element} this
14261          */
14262         setDisplayed : function(value) {
14263             if(typeof value == "boolean"){
14264                value = value ? getDisplay(this.dom) : NONE;
14265             }
14266             this.setStyle(DISPLAY, value);
14267             return this;
14268         },
14269
14270         // private
14271         fixDisplay : function(){
14272             var me = this;
14273             if (me.isStyle(DISPLAY, NONE)) {
14274                 me.setStyle(VISIBILITY, HIDDEN);
14275                 me.setStyle(DISPLAY, getDisplay(this.dom)); // first try reverting to default
14276                 if (me.isStyle(DISPLAY, NONE)) { // if that fails, default to block
14277                     me.setStyle(DISPLAY, "block");
14278                 }
14279             }
14280         },
14281
14282         /**
14283          * Hide this element - Uses display mode to determine whether to use "display" or "visibility". See {@link #setVisible}.
14284          * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
14285          * @return {Ext.core.Element} this
14286          */
14287         hide : function(animate){
14288             // hideMode override
14289             if (typeof animate == 'string'){
14290                 this.setVisible(false, animate);
14291                 return this;
14292             }
14293             this.setVisible(false, this.anim(animate));
14294             return this;
14295         },
14296
14297         /**
14298         * Show this element - Uses display mode to determine whether to use "display" or "visibility". See {@link #setVisible}.
14299         * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
14300          * @return {Ext.core.Element} this
14301          */
14302         show : function(animate){
14303             // hideMode override
14304             if (typeof animate == 'string'){
14305                 this.setVisible(true, animate);
14306                 return this;
14307             }
14308             this.setVisible(true, this.anim(animate));
14309             return this;
14310         }
14311     };
14312 }());
14313 /**
14314  * @class Ext.core.Element
14315  */
14316 Ext.applyIf(Ext.core.Element.prototype, {
14317     // @private override base Ext.util.Animate mixin for animate for backwards compatibility
14318     animate: function(config) {
14319         var me = this;
14320         if (!me.id) {
14321             me = Ext.get(me.dom);
14322         }
14323         if (Ext.fx.Manager.hasFxBlock(me.id)) {
14324             return me;
14325         }
14326         Ext.fx.Manager.queueFx(Ext.create('Ext.fx.Anim', me.anim(config)));
14327         return this;
14328     },
14329
14330     // @private override base Ext.util.Animate mixin for animate for backwards compatibility
14331     anim: function(config) {
14332         if (!Ext.isObject(config)) {
14333             return (config) ? {} : false;
14334         }
14335
14336         var me = this,
14337             duration = config.duration || Ext.fx.Anim.prototype.duration,
14338             easing = config.easing || 'ease',
14339             animConfig;
14340
14341         if (config.stopAnimation) {
14342             me.stopAnimation();
14343         }
14344
14345         Ext.applyIf(config, Ext.fx.Manager.getFxDefaults(me.id));
14346
14347         // Clear any 'paused' defaults.
14348         Ext.fx.Manager.setFxDefaults(me.id, {
14349             delay: 0
14350         });
14351
14352         animConfig = {
14353             target: me,
14354             remove: config.remove,
14355             alternate: config.alternate || false,
14356             duration: duration,
14357             easing: easing,
14358             callback: config.callback,
14359             listeners: config.listeners,
14360             iterations: config.iterations || 1,
14361             scope: config.scope,
14362             block: config.block,
14363             concurrent: config.concurrent,
14364             delay: config.delay || 0,
14365             paused: true,
14366             keyframes: config.keyframes,
14367             from: config.from || {},
14368             to: Ext.apply({}, config)
14369         };
14370         Ext.apply(animConfig.to, config.to);
14371
14372         // Anim API properties - backward compat
14373         delete animConfig.to.to;
14374         delete animConfig.to.from;
14375         delete animConfig.to.remove;
14376         delete animConfig.to.alternate;
14377         delete animConfig.to.keyframes;
14378         delete animConfig.to.iterations;
14379         delete animConfig.to.listeners;
14380         delete animConfig.to.target;
14381         delete animConfig.to.paused;
14382         delete animConfig.to.callback;
14383         delete animConfig.to.scope;
14384         delete animConfig.to.duration;
14385         delete animConfig.to.easing;
14386         delete animConfig.to.concurrent;
14387         delete animConfig.to.block;
14388         delete animConfig.to.stopAnimation;
14389         delete animConfig.to.delay;
14390         return animConfig;
14391     },
14392
14393     /**
14394      * Slides the element into view.  An anchor point can be optionally passed to set the point of
14395      * origin for the slide effect.  This function automatically handles wrapping the element with
14396      * a fixed-size container if needed.  See the Fx class overview for valid anchor point options.
14397      * Usage:
14398      *<pre><code>
14399 // default: slide the element in from the top
14400 el.slideIn();
14401
14402 // custom: slide the element in from the right with a 2-second duration
14403 el.slideIn('r', { duration: 2 });
14404
14405 // common config options shown with default values
14406 el.slideIn('t', {
14407     easing: 'easeOut',
14408     duration: 500
14409 });
14410 </code></pre>
14411      * @param {String} anchor (optional) One of the valid Fx anchor positions (defaults to top: 't')
14412      * @param {Object} options (optional) Object literal with any of the Fx config options
14413      * @return {Ext.core.Element} The Element
14414      */
14415     slideIn: function(anchor, obj, slideOut) { 
14416         var me = this,
14417             elStyle = me.dom.style,
14418             beforeAnim, wrapAnim;
14419
14420         anchor = anchor || "t";
14421         obj = obj || {};
14422
14423         beforeAnim = function() {
14424             var animScope = this,
14425                 listeners = obj.listeners,
14426                 box, position, restoreSize, wrap, anim;
14427
14428             if (!slideOut) {
14429                 me.fixDisplay();
14430             }
14431
14432             box = me.getBox();
14433             if ((anchor == 't' || anchor == 'b') && box.height == 0) {
14434                 box.height = me.dom.scrollHeight;
14435             }
14436             else if ((anchor == 'l' || anchor == 'r') && box.width == 0) {
14437                 box.width = me.dom.scrollWidth;
14438             }
14439             
14440             position = me.getPositioning();
14441             me.setSize(box.width, box.height);
14442
14443             wrap = me.wrap({
14444                 style: {
14445                     visibility: slideOut ? 'visible' : 'hidden'
14446                 }
14447             });
14448             wrap.setPositioning(position);
14449             if (wrap.isStyle('position', 'static')) {
14450                 wrap.position('relative');
14451             }
14452             me.clearPositioning('auto');
14453             wrap.clip();
14454
14455             // This element is temporarily positioned absolute within its wrapper.
14456             // Restore to its default, CSS-inherited visibility setting.
14457             // We cannot explicitly poke visibility:visible into its style because that overrides the visibility of the wrap.
14458             me.setStyle({
14459                 visibility: '',
14460                 position: 'absolute'
14461             });
14462             if (slideOut) {
14463                 wrap.setSize(box.width, box.height);
14464             }
14465
14466             switch (anchor) {
14467                 case 't':
14468                     anim = {
14469                         from: {
14470                             width: box.width + 'px',
14471                             height: '0px'
14472                         },
14473                         to: {
14474                             width: box.width + 'px',
14475                             height: box.height + 'px'
14476                         }
14477                     };
14478                     elStyle.bottom = '0px';
14479                     break;
14480                 case 'l':
14481                     anim = {
14482                         from: {
14483                             width: '0px',
14484                             height: box.height + 'px'
14485                         },
14486                         to: {
14487                             width: box.width + 'px',
14488                             height: box.height + 'px'
14489                         }
14490                     };
14491                     elStyle.right = '0px';
14492                     break;
14493                 case 'r':
14494                     anim = {
14495                         from: {
14496                             x: box.x + box.width,
14497                             width: '0px',
14498                             height: box.height + 'px'
14499                         },
14500                         to: {
14501                             x: box.x,
14502                             width: box.width + 'px',
14503                             height: box.height + 'px'
14504                         }
14505                     };
14506                     break;
14507                 case 'b':
14508                     anim = {
14509                         from: {
14510                             y: box.y + box.height,
14511                             width: box.width + 'px',
14512                             height: '0px'
14513                         },
14514                         to: {
14515                             y: box.y,
14516                             width: box.width + 'px',
14517                             height: box.height + 'px'
14518                         }
14519                     };
14520                     break;
14521                 case 'tl':
14522                     anim = {
14523                         from: {
14524                             x: box.x,
14525                             y: box.y,
14526                             width: '0px',
14527                             height: '0px'
14528                         },
14529                         to: {
14530                             width: box.width + 'px',
14531                             height: box.height + 'px'
14532                         }
14533                     };
14534                     elStyle.bottom = '0px';
14535                     elStyle.right = '0px';
14536                     break;
14537                 case 'bl':
14538                     anim = {
14539                         from: {
14540                             x: box.x + box.width,
14541                             width: '0px',
14542                             height: '0px'
14543                         },
14544                         to: {
14545                             x: box.x,
14546                             width: box.width + 'px',
14547                             height: box.height + 'px'
14548                         }
14549                     };
14550                     elStyle.right = '0px';
14551                     break;
14552                 case 'br':
14553                     anim = {
14554                         from: {
14555                             x: box.x + box.width,
14556                             y: box.y + box.height,
14557                             width: '0px',
14558                             height: '0px'
14559                         },
14560                         to: {
14561                             x: box.x,
14562                             y: box.y,
14563                             width: box.width + 'px',
14564                             height: box.height + 'px'
14565                         }
14566                     };
14567                     break;
14568                 case 'tr':
14569                     anim = {
14570                         from: {
14571                             y: box.y + box.height,
14572                             width: '0px',
14573                             height: '0px'
14574                         },
14575                         to: {
14576                             y: box.y,
14577                             width: box.width + 'px',
14578                             height: box.height + 'px'
14579                         }
14580                     };
14581                     elStyle.bottom = '0px';
14582                     break;
14583             }
14584
14585             wrap.show();
14586             wrapAnim = Ext.apply({}, obj);
14587             delete wrapAnim.listeners;
14588             wrapAnim = Ext.create('Ext.fx.Anim', Ext.applyIf(wrapAnim, {
14589                 target: wrap,
14590                 duration: 500,
14591                 easing: 'ease-out',
14592                 from: slideOut ? anim.to : anim.from,
14593                 to: slideOut ? anim.from : anim.to
14594             }));
14595
14596             // In the absence of a callback, this listener MUST be added first
14597             wrapAnim.on('afteranimate', function() {
14598                 if (slideOut) {
14599                     me.setPositioning(position);
14600                     if (obj.useDisplay) {
14601                         me.setDisplayed(false);
14602                     } else {
14603                         me.hide();   
14604                     }
14605                 }
14606                 else {
14607                     me.clearPositioning();
14608                     me.setPositioning(position);
14609                 }
14610                 if (wrap.dom) {
14611                     wrap.dom.parentNode.insertBefore(me.dom, wrap.dom); 
14612                     wrap.remove();
14613                 }
14614                 me.setSize(box.width, box.height);
14615                 animScope.end();
14616             });
14617             // Add configured listeners after
14618             if (listeners) {
14619                 wrapAnim.on(listeners);
14620             }
14621         };
14622
14623         me.animate({
14624             duration: obj.duration ? obj.duration * 2 : 1000,
14625             listeners: {
14626                 beforeanimate: {
14627                     fn: beforeAnim
14628                 },
14629                 afteranimate: {
14630                     fn: function() {
14631                         if (wrapAnim && wrapAnim.running) {
14632                             wrapAnim.end();
14633                         }
14634                     }
14635                 }
14636             }
14637         });
14638         return me;
14639     },
14640
14641     
14642     /**
14643      * Slides the element out of view.  An anchor point can be optionally passed to set the end point
14644      * for the slide effect.  When the effect is completed, the element will be hidden (visibility = 
14645      * 'hidden') but block elements will still take up space in the document.  The element must be removed
14646      * from the DOM using the 'remove' config option if desired.  This function automatically handles 
14647      * wrapping the element with a fixed-size container if needed.  See the Fx class overview for valid anchor point options.
14648      * Usage:
14649      *<pre><code>
14650 // default: slide the element out to the top
14651 el.slideOut();
14652
14653 // custom: slide the element out to the right with a 2-second duration
14654 el.slideOut('r', { duration: 2 });
14655
14656 // common config options shown with default values
14657 el.slideOut('t', {
14658     easing: 'easeOut',
14659     duration: 500,
14660     remove: false,
14661     useDisplay: false
14662 });
14663 </code></pre>
14664      * @param {String} anchor (optional) One of the valid Fx anchor positions (defaults to top: 't')
14665      * @param {Object} options (optional) Object literal with any of the Fx config options
14666      * @return {Ext.core.Element} The Element
14667      */
14668     slideOut: function(anchor, o) {
14669         return this.slideIn(anchor, o, true);
14670     },
14671
14672     /**
14673      * Fades the element out while slowly expanding it in all directions.  When the effect is completed, the 
14674      * element will be hidden (visibility = 'hidden') but block elements will still take up space in the document.
14675      * Usage:
14676      *<pre><code>
14677 // default
14678 el.puff();
14679
14680 // common config options shown with default values
14681 el.puff({
14682     easing: 'easeOut',
14683     duration: 500,
14684     useDisplay: false
14685 });
14686 </code></pre>
14687      * @param {Object} options (optional) Object literal with any of the Fx config options
14688      * @return {Ext.core.Element} The Element
14689      */
14690
14691     puff: function(obj) {
14692         var me = this,
14693             beforeAnim;
14694         obj = Ext.applyIf(obj || {}, {
14695             easing: 'ease-out',
14696             duration: 500,
14697             useDisplay: false
14698         });
14699
14700         beforeAnim = function() {
14701             me.clearOpacity();
14702             me.show();
14703
14704             var box = me.getBox(),
14705                 fontSize = me.getStyle('fontSize'),
14706                 position = me.getPositioning();
14707             this.to = {
14708                 width: box.width * 2,
14709                 height: box.height * 2,
14710                 x: box.x - (box.width / 2),
14711                 y: box.y - (box.height /2),
14712                 opacity: 0,
14713                 fontSize: '200%'
14714             };
14715             this.on('afteranimate',function() {
14716                 if (me.dom) {
14717                     if (obj.useDisplay) {
14718                         me.setDisplayed(false);
14719                     } else {
14720                         me.hide();
14721                     }
14722                     me.clearOpacity();  
14723                     me.setPositioning(position);
14724                     me.setStyle({fontSize: fontSize});
14725                 }
14726             });
14727         };
14728
14729         me.animate({
14730             duration: obj.duration,
14731             easing: obj.easing,
14732             listeners: {
14733                 beforeanimate: {
14734                     fn: beforeAnim
14735                 }
14736             }
14737         });
14738         return me;
14739     },
14740
14741     /**
14742      * Blinks the element as if it was clicked and then collapses on its center (similar to switching off a television).
14743      * When the effect is completed, the element will be hidden (visibility = 'hidden') but block elements will still 
14744      * take up space in the document. The element must be removed from the DOM using the 'remove' config option if desired.
14745      * Usage:
14746      *<pre><code>
14747 // default
14748 el.switchOff();
14749
14750 // all config options shown with default values
14751 el.switchOff({
14752     easing: 'easeIn',
14753     duration: .3,
14754     remove: false,
14755     useDisplay: false
14756 });
14757 </code></pre>
14758      * @param {Object} options (optional) Object literal with any of the Fx config options
14759      * @return {Ext.core.Element} The Element
14760      */
14761     switchOff: function(obj) {
14762         var me = this,
14763             beforeAnim;
14764         
14765         obj = Ext.applyIf(obj || {}, {
14766             easing: 'ease-in',
14767             duration: 500,
14768             remove: false,
14769             useDisplay: false
14770         });
14771
14772         beforeAnim = function() {
14773             var animScope = this,
14774                 size = me.getSize(),
14775                 xy = me.getXY(),
14776                 keyframe, position;
14777             me.clearOpacity();
14778             me.clip();
14779             position = me.getPositioning();
14780
14781             keyframe = Ext.create('Ext.fx.Animator', {
14782                 target: me,
14783                 duration: obj.duration,
14784                 easing: obj.easing,
14785                 keyframes: {
14786                     33: {
14787                         opacity: 0.3
14788                     },
14789                     66: {
14790                         height: 1,
14791                         y: xy[1] + size.height / 2
14792                     },
14793                     100: {
14794                         width: 1,
14795                         x: xy[0] + size.width / 2
14796                     }
14797                 }
14798             });
14799             keyframe.on('afteranimate', function() {
14800                 if (obj.useDisplay) {
14801                     me.setDisplayed(false);
14802                 } else {
14803                     me.hide();
14804                 }  
14805                 me.clearOpacity();
14806                 me.setPositioning(position);
14807                 me.setSize(size);
14808                 animScope.end();
14809             });
14810         };
14811         me.animate({
14812             duration: (obj.duration * 2),
14813             listeners: {
14814                 beforeanimate: {
14815                     fn: beforeAnim
14816                 }
14817             }
14818         });
14819         return me;
14820     },
14821
14822    /**
14823     * Shows a ripple of exploding, attenuating borders to draw attention to an Element.
14824     * Usage:
14825 <pre><code>
14826 // default: a single light blue ripple
14827 el.frame();
14828
14829 // custom: 3 red ripples lasting 3 seconds total
14830 el.frame("#ff0000", 3, { duration: 3 });
14831
14832 // common config options shown with default values
14833 el.frame("#C3DAF9", 1, {
14834     duration: 1 //duration of each individual ripple.
14835     // Note: Easing is not configurable and will be ignored if included
14836 });
14837 </code></pre>
14838     * @param {String} color (optional) The color of the border.  Should be a 6 char hex color without the leading # (defaults to light blue: 'C3DAF9').
14839     * @param {Number} count (optional) The number of ripples to display (defaults to 1)
14840     * @param {Object} options (optional) Object literal with any of the Fx config options
14841     * @return {Ext.core.Element} The Element
14842     */
14843     frame : function(color, count, obj){
14844         var me = this,
14845             beforeAnim;
14846
14847         color = color || '#C3DAF9';
14848         count = count || 1;
14849         obj = obj || {};
14850
14851         beforeAnim = function() {
14852             me.show();
14853             var animScope = this,
14854                 box = me.getBox(),
14855                 proxy = Ext.getBody().createChild({
14856                     style: {
14857                         position : 'absolute',
14858                         'pointer-events': 'none',
14859                         'z-index': 35000,
14860                         border : '0px solid ' + color
14861                     }
14862                 }),
14863                 proxyAnim;
14864             proxyAnim = Ext.create('Ext.fx.Anim', {
14865                 target: proxy,
14866                 duration: obj.duration || 1000,
14867                 iterations: count,
14868                 from: {
14869                     top: box.y,
14870                     left: box.x,
14871                     borderWidth: 0,
14872                     opacity: 1,
14873                     height: box.height,
14874                     width: box.width
14875                 },
14876                 to: {
14877                     top: box.y - 20,
14878                     left: box.x - 20,
14879                     borderWidth: 10,
14880                     opacity: 0,
14881                     height: box.height + 40,
14882                     width: box.width + 40
14883                 }
14884             });
14885             proxyAnim.on('afteranimate', function() {
14886                 proxy.remove();
14887                 animScope.end();
14888             });
14889         };
14890
14891         me.animate({
14892             duration: (obj.duration * 2) || 2000,
14893             listeners: {
14894                 beforeanimate: {
14895                     fn: beforeAnim
14896                 }
14897             }
14898         });
14899         return me;
14900     },
14901
14902     /**
14903      * Slides the element while fading it out of view.  An anchor point can be optionally passed to set the 
14904      * ending point of the effect.
14905      * Usage:
14906      *<pre><code>
14907 // default: slide the element downward while fading out
14908 el.ghost();
14909
14910 // custom: slide the element out to the right with a 2-second duration
14911 el.ghost('r', { duration: 2 });
14912
14913 // common config options shown with default values
14914 el.ghost('b', {
14915     easing: 'easeOut',
14916     duration: 500
14917 });
14918 </code></pre>
14919      * @param {String} anchor (optional) One of the valid Fx anchor positions (defaults to bottom: 'b')
14920      * @param {Object} options (optional) Object literal with any of the Fx config options
14921      * @return {Ext.core.Element} The Element
14922      */
14923     ghost: function(anchor, obj) {
14924         var me = this,
14925             beforeAnim;
14926
14927         anchor = anchor || "b";
14928         beforeAnim = function() {
14929             var width = me.getWidth(),
14930                 height = me.getHeight(),
14931                 xy = me.getXY(),
14932                 position = me.getPositioning(),
14933                 to = {
14934                     opacity: 0
14935                 };
14936             switch (anchor) {
14937                 case 't':
14938                     to.y = xy[1] - height;
14939                     break;
14940                 case 'l':
14941                     to.x = xy[0] - width;
14942                     break;
14943                 case 'r':
14944                     to.x = xy[0] + width;
14945                     break;
14946                 case 'b':
14947                     to.y = xy[1] + height;
14948                     break;
14949                 case 'tl':
14950                     to.x = xy[0] - width;
14951                     to.y = xy[1] - height;
14952                     break;
14953                 case 'bl':
14954                     to.x = xy[0] - width;
14955                     to.y = xy[1] + height;
14956                     break;
14957                 case 'br':
14958                     to.x = xy[0] + width;
14959                     to.y = xy[1] + height;
14960                     break;
14961                 case 'tr':
14962                     to.x = xy[0] + width;
14963                     to.y = xy[1] - height;
14964                     break;
14965             }
14966             this.to = to;
14967             this.on('afteranimate', function () {
14968                 if (me.dom) {
14969                     me.hide();
14970                     me.clearOpacity();
14971                     me.setPositioning(position);
14972                 }
14973             });
14974         };
14975
14976         me.animate(Ext.applyIf(obj || {}, {
14977             duration: 500,
14978             easing: 'ease-out',
14979             listeners: {
14980                 beforeanimate: {
14981                     fn: beforeAnim
14982                 }
14983             }
14984         }));
14985         return me;
14986     },
14987
14988     /**
14989      * Highlights the Element by setting a color (applies to the background-color by default, but can be
14990      * changed using the "attr" config option) and then fading back to the original color. If no original
14991      * color is available, you should provide the "endColor" config option which will be cleared after the animation.
14992      * Usage:
14993 <pre><code>
14994 // default: highlight background to yellow
14995 el.highlight();
14996
14997 // custom: highlight foreground text to blue for 2 seconds
14998 el.highlight("0000ff", { attr: 'color', duration: 2 });
14999
15000 // common config options shown with default values
15001 el.highlight("ffff9c", {
15002     attr: "backgroundColor", //can be any valid CSS property (attribute) that supports a color value
15003     endColor: (current color) or "ffffff",
15004     easing: 'easeIn',
15005     duration: 1000
15006 });
15007 </code></pre>
15008      * @param {String} color (optional) The highlight color. Should be a 6 char hex color without the leading # (defaults to yellow: 'ffff9c')
15009      * @param {Object} options (optional) Object literal with any of the Fx config options
15010      * @return {Ext.core.Element} The Element
15011      */ 
15012     highlight: function(color, o) {
15013         var me = this,
15014             dom = me.dom,
15015             from = {},
15016             restore, to, attr, lns, event, fn;
15017
15018         o = o || {};
15019         lns = o.listeners || {};
15020         attr = o.attr || 'backgroundColor';
15021         from[attr] = color || 'ffff9c';
15022         
15023         if (!o.to) {
15024             to = {};
15025             to[attr] = o.endColor || me.getColor(attr, 'ffffff', '');
15026         }
15027         else {
15028             to = o.to;
15029         }
15030         
15031         // Don't apply directly on lns, since we reference it in our own callbacks below
15032         o.listeners = Ext.apply(Ext.apply({}, lns), {
15033             beforeanimate: function() {
15034                 restore = dom.style[attr];
15035                 me.clearOpacity();
15036                 me.show();
15037                 
15038                 event = lns.beforeanimate;
15039                 if (event) {
15040                     fn = event.fn || event;
15041                     return fn.apply(event.scope || lns.scope || window, arguments);
15042                 }
15043             },
15044             afteranimate: function() {
15045                 if (dom) {
15046                     dom.style[attr] = restore;
15047                 }
15048                 
15049                 event = lns.afteranimate;
15050                 if (event) {
15051                     fn = event.fn || event;
15052                     fn.apply(event.scope || lns.scope || window, arguments);
15053                 }
15054             }
15055         });
15056
15057         me.animate(Ext.apply({}, o, {
15058             duration: 1000,
15059             easing: 'ease-in',
15060             from: from,
15061             to: to
15062         }));
15063         return me;
15064     },
15065
15066    /**
15067     * @deprecated 4.0
15068     * Creates a pause before any subsequent queued effects begin.  If there are
15069     * no effects queued after the pause it will have no effect.
15070     * Usage:
15071 <pre><code>
15072 el.pause(1);
15073 </code></pre>
15074     * @param {Number} seconds The length of time to pause (in seconds)
15075     * @return {Ext.Element} The Element
15076     */
15077     pause: function(ms) {
15078         var me = this;
15079         Ext.fx.Manager.setFxDefaults(me.id, {
15080             delay: ms
15081         });
15082         return me;
15083     },
15084
15085    /**
15086     * Fade an element in (from transparent to opaque).  The ending opacity can be specified
15087     * using the <tt>{@link #endOpacity}</tt> config option.
15088     * Usage:
15089 <pre><code>
15090 // default: fade in from opacity 0 to 100%
15091 el.fadeIn();
15092
15093 // custom: fade in from opacity 0 to 75% over 2 seconds
15094 el.fadeIn({ endOpacity: .75, duration: 2});
15095
15096 // common config options shown with default values
15097 el.fadeIn({
15098     endOpacity: 1, //can be any value between 0 and 1 (e.g. .5)
15099     easing: 'easeOut',
15100     duration: 500
15101 });
15102 </code></pre>
15103     * @param {Object} options (optional) Object literal with any of the Fx config options
15104     * @return {Ext.Element} The Element
15105     */
15106     fadeIn: function(o) {
15107         this.animate(Ext.apply({}, o, {
15108             opacity: 1
15109         }));
15110         return this;
15111     },
15112
15113    /**
15114     * Fade an element out (from opaque to transparent).  The ending opacity can be specified
15115     * using the <tt>{@link #endOpacity}</tt> config option.  Note that IE may require
15116     * <tt>{@link #useDisplay}:true</tt> in order to redisplay correctly.
15117     * Usage:
15118 <pre><code>
15119 // default: fade out from the element's current opacity to 0
15120 el.fadeOut();
15121
15122 // custom: fade out from the element's current opacity to 25% over 2 seconds
15123 el.fadeOut({ endOpacity: .25, duration: 2});
15124
15125 // common config options shown with default values
15126 el.fadeOut({
15127     endOpacity: 0, //can be any value between 0 and 1 (e.g. .5)
15128     easing: 'easeOut',
15129     duration: 500,
15130     remove: false,
15131     useDisplay: false
15132 });
15133 </code></pre>
15134     * @param {Object} options (optional) Object literal with any of the Fx config options
15135     * @return {Ext.Element} The Element
15136     */
15137     fadeOut: function(o) {
15138         this.animate(Ext.apply({}, o, {
15139             opacity: 0
15140         }));
15141         return this;
15142     },
15143
15144    /**
15145     * @deprecated 4.0
15146     * Animates the transition of an element's dimensions from a starting height/width
15147     * to an ending height/width.  This method is a convenience implementation of {@link shift}.
15148     * Usage:
15149 <pre><code>
15150 // change height and width to 100x100 pixels
15151 el.scale(100, 100);
15152
15153 // common config options shown with default values.  The height and width will default to
15154 // the element&#39;s existing values if passed as null.
15155 el.scale(
15156     [element&#39;s width],
15157     [element&#39;s height], {
15158         easing: 'easeOut',
15159         duration: .35
15160     }
15161 );
15162 </code></pre>
15163     * @param {Number} width  The new width (pass undefined to keep the original width)
15164     * @param {Number} height  The new height (pass undefined to keep the original height)
15165     * @param {Object} options (optional) Object literal with any of the Fx config options
15166     * @return {Ext.Element} The Element
15167     */
15168     scale: function(w, h, o) {
15169         this.animate(Ext.apply({}, o, {
15170             width: w,
15171             height: h
15172         }));
15173         return this;
15174     },
15175
15176    /**
15177     * @deprecated 4.0
15178     * Animates the transition of any combination of an element's dimensions, xy position and/or opacity.
15179     * Any of these properties not specified in the config object will not be changed.  This effect 
15180     * requires that at least one new dimension, position or opacity setting must be passed in on
15181     * the config object in order for the function to have any effect.
15182     * Usage:
15183 <pre><code>
15184 // slide the element horizontally to x position 200 while changing the height and opacity
15185 el.shift({ x: 200, height: 50, opacity: .8 });
15186
15187 // common config options shown with default values.
15188 el.shift({
15189     width: [element&#39;s width],
15190     height: [element&#39;s height],
15191     x: [element&#39;s x position],
15192     y: [element&#39;s y position],
15193     opacity: [element&#39;s opacity],
15194     easing: 'easeOut',
15195     duration: .35
15196 });
15197 </code></pre>
15198     * @param {Object} options  Object literal with any of the Fx config options
15199     * @return {Ext.Element} The Element
15200     */
15201     shift: function(config) {
15202         this.animate(config);
15203         return this;
15204     }
15205 });
15206
15207 /**
15208  * @class Ext.core.Element
15209  */
15210 Ext.applyIf(Ext.core.Element, {
15211     unitRe: /\d+(px|em|%|en|ex|pt|in|cm|mm|pc)$/i,
15212     camelRe: /(-[a-z])/gi,
15213     opacityRe: /alpha\(opacity=(.*)\)/i,
15214     cssRe: /([a-z0-9-]+)\s*:\s*([^;\s]+(?:\s*[^;\s]+)*);?/gi,
15215     propertyCache: {},
15216     defaultUnit : "px",
15217     borders: {l: 'border-left-width', r: 'border-right-width', t: 'border-top-width', b: 'border-bottom-width'},
15218     paddings: {l: 'padding-left', r: 'padding-right', t: 'padding-top', b: 'padding-bottom'},
15219     margins: {l: 'margin-left', r: 'margin-right', t: 'margin-top', b: 'margin-bottom'},
15220
15221     // Reference the prototype's version of the method. Signatures are identical.
15222     addUnits : Ext.core.Element.prototype.addUnits,
15223
15224     /**
15225      * Parses a number or string representing margin sizes into an object. Supports CSS-style margin declarations
15226      * (e.g. 10, "10", "10 10", "10 10 10" and "10 10 10 10" are all valid options and would return the same result)
15227      * @static
15228      * @param {Number|String} box The encoded margins
15229      * @return {Object} An object with margin sizes for top, right, bottom and left
15230      */
15231     parseBox : function(box) {
15232         if (Ext.isObject(box)) {
15233             return {
15234                 top: box.top || 0,
15235                 right: box.right || 0,
15236                 bottom: box.bottom || 0,
15237                 left: box.left || 0
15238             };
15239         } else {
15240             if (typeof box != 'string') {
15241                 box = box.toString();
15242             }
15243             var parts  = box.split(' '),
15244                 ln = parts.length;
15245     
15246             if (ln == 1) {
15247                 parts[1] = parts[2] = parts[3] = parts[0];
15248             }
15249             else if (ln == 2) {
15250                 parts[2] = parts[0];
15251                 parts[3] = parts[1];
15252             }
15253             else if (ln == 3) {
15254                 parts[3] = parts[1];
15255             }
15256     
15257             return {
15258                 top   :parseFloat(parts[0]) || 0,
15259                 right :parseFloat(parts[1]) || 0,
15260                 bottom:parseFloat(parts[2]) || 0,
15261                 left  :parseFloat(parts[3]) || 0
15262             };
15263         }
15264         
15265     },
15266     
15267     /**
15268      * Parses a number or string representing margin sizes into an object. Supports CSS-style margin declarations
15269      * (e.g. 10, "10", "10 10", "10 10 10" and "10 10 10 10" are all valid options and would return the same result)
15270      * @static
15271      * @param {Number|String} box The encoded margins
15272      * @param {String} units The type of units to add
15273      * @return {String} An string with unitized (px if units is not specified) metrics for top, right, bottom and left
15274      */
15275     unitizeBox : function(box, units) {
15276         var A = this.addUnits,
15277             B = this.parseBox(box);
15278             
15279         return A(B.top, units) + ' ' +
15280                A(B.right, units) + ' ' +
15281                A(B.bottom, units) + ' ' +
15282                A(B.left, units);
15283         
15284     },
15285
15286     // private
15287     camelReplaceFn : function(m, a) {
15288         return a.charAt(1).toUpperCase();
15289     },
15290
15291     /**
15292      * Normalizes CSS property keys from dash delimited to camel case JavaScript Syntax.
15293      * For example:
15294      * <ul>
15295      *  <li>border-width -> borderWidth</li>
15296      *  <li>padding-top -> paddingTop</li>
15297      * </ul>
15298      * @static
15299      * @param {String} prop The property to normalize
15300      * @return {String} The normalized string
15301      */
15302     normalize : function(prop) {
15303         if (prop == 'float') {
15304             prop = Ext.supports.Float ? 'cssFloat' : 'styleFloat';
15305         }
15306         return this.propertyCache[prop] || (this.propertyCache[prop] = prop.replace(this.camelRe, this.camelReplaceFn));
15307     },
15308
15309     /**
15310      * Retrieves the document height
15311      * @static
15312      * @return {Number} documentHeight
15313      */
15314     getDocumentHeight: function() {
15315         return Math.max(!Ext.isStrict ? document.body.scrollHeight : document.documentElement.scrollHeight, this.getViewportHeight());
15316     },
15317
15318     /**
15319      * Retrieves the document width
15320      * @static
15321      * @return {Number} documentWidth
15322      */
15323     getDocumentWidth: function() {
15324         return Math.max(!Ext.isStrict ? document.body.scrollWidth : document.documentElement.scrollWidth, this.getViewportWidth());
15325     },
15326
15327     /**
15328      * Retrieves the viewport height of the window.
15329      * @static
15330      * @return {Number} viewportHeight
15331      */
15332     getViewportHeight: function(){
15333         return window.innerHeight;
15334     },
15335
15336     /**
15337      * Retrieves the viewport width of the window.
15338      * @static
15339      * @return {Number} viewportWidth
15340      */
15341     getViewportWidth : function() {
15342         return window.innerWidth;
15343     },
15344
15345     /**
15346      * Retrieves the viewport size of the window.
15347      * @static
15348      * @return {Object} object containing width and height properties
15349      */
15350     getViewSize : function() {
15351         return {
15352             width: window.innerWidth,
15353             height: window.innerHeight
15354         };
15355     },
15356
15357     /**
15358      * Retrieves the current orientation of the window. This is calculated by
15359      * determing if the height is greater than the width.
15360      * @static
15361      * @return {String} Orientation of window: 'portrait' or 'landscape'
15362      */
15363     getOrientation : function() {
15364         if (Ext.supports.OrientationChange) {
15365             return (window.orientation == 0) ? 'portrait' : 'landscape';
15366         }
15367         
15368         return (window.innerHeight > window.innerWidth) ? 'portrait' : 'landscape';
15369     },
15370
15371     /** 
15372      * Returns the top Element that is located at the passed coordinates
15373      * @static
15374      * @param {Number} x The x coordinate
15375      * @param {Number} x The y coordinate
15376      * @return {String} The found Element
15377      */
15378     fromPoint: function(x, y) {
15379         return Ext.get(document.elementFromPoint(x, y));
15380     },
15381     
15382     /**
15383      * Converts a CSS string into an object with a property for each style.
15384      * <p>
15385      * The sample code below would return an object with 2 properties, one
15386      * for background-color and one for color.</p>
15387      * <pre><code>
15388 var css = 'background-color: red;color: blue; ';
15389 console.log(Ext.core.Element.parseStyles(css));
15390      * </code></pre>
15391      * @static
15392      * @param {String} styles A CSS string
15393      * @return {Object} styles
15394      */
15395     parseStyles: function(styles){
15396         var out = {},
15397             cssRe = this.cssRe,
15398             matches;
15399             
15400         if (styles) {
15401             // Since we're using the g flag on the regex, we need to set the lastIndex.
15402             // This automatically happens on some implementations, but not others, see:
15403             // http://stackoverflow.com/questions/2645273/javascript-regular-expression-literal-persists-between-function-calls
15404             // http://blog.stevenlevithan.com/archives/fixing-javascript-regexp
15405             cssRe.lastIndex = 0;
15406             while ((matches = cssRe.exec(styles))) {
15407                 out[matches[1]] = matches[2];
15408             }
15409         }
15410         return out;
15411     }
15412 });
15413
15414 /**
15415  * @class Ext.CompositeElementLite
15416  * <p>This class encapsulates a <i>collection</i> of DOM elements, providing methods to filter
15417  * members, or to perform collective actions upon the whole set.</p>
15418  * <p>Although they are not listed, this class supports all of the methods of {@link Ext.core.Element} and
15419  * {@link Ext.fx.Anim}. The methods from these classes will be performed on all the elements in this collection.</p>
15420  * Example:<pre><code>
15421 var els = Ext.select("#some-el div.some-class");
15422 // or select directly from an existing element
15423 var el = Ext.get('some-el');
15424 el.select('div.some-class');
15425
15426 els.setWidth(100); // all elements become 100 width
15427 els.hide(true); // all elements fade out and hide
15428 // or
15429 els.setWidth(100).hide(true);
15430 </code></pre>
15431  */
15432 Ext.CompositeElementLite = function(els, root){
15433     /**
15434      * <p>The Array of DOM elements which this CompositeElement encapsulates. Read-only.</p>
15435      * <p>This will not <i>usually</i> be accessed in developers' code, but developers wishing
15436      * to augment the capabilities of the CompositeElementLite class may use it when adding
15437      * methods to the class.</p>
15438      * <p>For example to add the <code>nextAll</code> method to the class to <b>add</b> all
15439      * following siblings of selected elements, the code would be</p><code><pre>
15440 Ext.override(Ext.CompositeElementLite, {
15441     nextAll: function() {
15442         var els = this.elements, i, l = els.length, n, r = [], ri = -1;
15443
15444 //      Loop through all elements in this Composite, accumulating
15445 //      an Array of all siblings.
15446         for (i = 0; i < l; i++) {
15447             for (n = els[i].nextSibling; n; n = n.nextSibling) {
15448                 r[++ri] = n;
15449             }
15450         }
15451
15452 //      Add all found siblings to this Composite
15453         return this.add(r);
15454     }
15455 });</pre></code>
15456      * @type Array
15457      * @property elements
15458      */
15459     this.elements = [];
15460     this.add(els, root);
15461     this.el = new Ext.core.Element.Flyweight();
15462 };
15463
15464 Ext.CompositeElementLite.prototype = {
15465     isComposite: true,
15466
15467     // private
15468     getElement : function(el){
15469         // Set the shared flyweight dom property to the current element
15470         var e = this.el;
15471         e.dom = el;
15472         e.id = el.id;
15473         return e;
15474     },
15475
15476     // private
15477     transformElement : function(el){
15478         return Ext.getDom(el);
15479     },
15480
15481     /**
15482      * Returns the number of elements in this Composite.
15483      * @return Number
15484      */
15485     getCount : function(){
15486         return this.elements.length;
15487     },
15488     /**
15489      * Adds elements to this Composite object.
15490      * @param {Mixed} els Either an Array of DOM elements to add, or another Composite object who's elements should be added.
15491      * @return {CompositeElement} This Composite object.
15492      */
15493     add : function(els, root){
15494         var me = this,
15495             elements = me.elements;
15496         if(!els){
15497             return this;
15498         }
15499         if(typeof els == "string"){
15500             els = Ext.core.Element.selectorFunction(els, root);
15501         }else if(els.isComposite){
15502             els = els.elements;
15503         }else if(!Ext.isIterable(els)){
15504             els = [els];
15505         }
15506
15507         for(var i = 0, len = els.length; i < len; ++i){
15508             elements.push(me.transformElement(els[i]));
15509         }
15510         return me;
15511     },
15512
15513     invoke : function(fn, args){
15514         var me = this,
15515             els = me.elements,
15516             len = els.length,
15517             e,
15518             i;
15519
15520         for(i = 0; i < len; i++) {
15521             e = els[i];
15522             if(e){
15523                 Ext.core.Element.prototype[fn].apply(me.getElement(e), args);
15524             }
15525         }
15526         return me;
15527     },
15528     /**
15529      * Returns a flyweight Element of the dom element object at the specified index
15530      * @param {Number} index
15531      * @return {Ext.core.Element}
15532      */
15533     item : function(index){
15534         var me = this,
15535             el = me.elements[index],
15536             out = null;
15537
15538         if(el){
15539             out = me.getElement(el);
15540         }
15541         return out;
15542     },
15543
15544     // fixes scope with flyweight
15545     addListener : function(eventName, handler, scope, opt){
15546         var els = this.elements,
15547             len = els.length,
15548             i, e;
15549
15550         for(i = 0; i<len; i++) {
15551             e = els[i];
15552             if(e) {
15553                 Ext.EventManager.on(e, eventName, handler, scope || e, opt);
15554             }
15555         }
15556         return this;
15557     },
15558     /**
15559      * <p>Calls the passed function for each element in this composite.</p>
15560      * @param {Function} fn The function to call. The function is passed the following parameters:<ul>
15561      * <li><b>el</b> : Element<div class="sub-desc">The current Element in the iteration.
15562      * <b>This is the flyweight (shared) Ext.core.Element instance, so if you require a
15563      * a reference to the dom node, use el.dom.</b></div></li>
15564      * <li><b>c</b> : Composite<div class="sub-desc">This Composite object.</div></li>
15565      * <li><b>idx</b> : Number<div class="sub-desc">The zero-based index in the iteration.</div></li>
15566      * </ul>
15567      * @param {Object} scope (optional) The scope (<i>this</i> reference) in which the function is executed. (defaults to the Element)
15568      * @return {CompositeElement} this
15569      */
15570     each : function(fn, scope){
15571         var me = this,
15572             els = me.elements,
15573             len = els.length,
15574             i, e;
15575
15576         for(i = 0; i<len; i++) {
15577             e = els[i];
15578             if(e){
15579                 e = this.getElement(e);
15580                 if(fn.call(scope || e, e, me, i) === false){
15581                     break;
15582                 }
15583             }
15584         }
15585         return me;
15586     },
15587
15588     /**
15589     * Clears this Composite and adds the elements passed.
15590     * @param {Mixed} els Either an array of DOM elements, or another Composite from which to fill this Composite.
15591     * @return {CompositeElement} this
15592     */
15593     fill : function(els){
15594         var me = this;
15595         me.elements = [];
15596         me.add(els);
15597         return me;
15598     },
15599
15600     /**
15601      * Filters this composite to only elements that match the passed selector.
15602      * @param {String/Function} selector A string CSS selector or a comparison function.
15603      * The comparison function will be called with the following arguments:<ul>
15604      * <li><code>el</code> : Ext.core.Element<div class="sub-desc">The current DOM element.</div></li>
15605      * <li><code>index</code> : Number<div class="sub-desc">The current index within the collection.</div></li>
15606      * </ul>
15607      * @return {CompositeElement} this
15608      */
15609     filter : function(selector){
15610         var els = [],
15611             me = this,
15612             fn = Ext.isFunction(selector) ? selector
15613                 : function(el){
15614                     return el.is(selector);
15615                 };
15616
15617         me.each(function(el, self, i) {
15618             if (fn(el, i) !== false) {
15619                 els[els.length] = me.transformElement(el);
15620             }
15621         });
15622         
15623         me.elements = els;
15624         return me;
15625     },
15626
15627     /**
15628      * Find the index of the passed element within the composite collection.
15629      * @param el {Mixed} The id of an element, or an Ext.core.Element, or an HtmlElement to find within the composite collection.
15630      * @return Number The index of the passed Ext.core.Element in the composite collection, or -1 if not found.
15631      */
15632     indexOf : function(el){
15633         return Ext.Array.indexOf(this.elements, this.transformElement(el));
15634     },
15635
15636     /**
15637     * Replaces the specified element with the passed element.
15638     * @param {Mixed} el The id of an element, the Element itself, the index of the element in this composite
15639     * to replace.
15640     * @param {Mixed} replacement The id of an element or the Element itself.
15641     * @param {Boolean} domReplace (Optional) True to remove and replace the element in the document too.
15642     * @return {CompositeElement} this
15643     */
15644     replaceElement : function(el, replacement, domReplace){
15645         var index = !isNaN(el) ? el : this.indexOf(el),
15646             d;
15647         if(index > -1){
15648             replacement = Ext.getDom(replacement);
15649             if(domReplace){
15650                 d = this.elements[index];
15651                 d.parentNode.insertBefore(replacement, d);
15652                 Ext.removeNode(d);
15653             }
15654             this.elements.splice(index, 1, replacement);
15655         }
15656         return this;
15657     },
15658
15659     /**
15660      * Removes all elements.
15661      */
15662     clear : function(){
15663         this.elements = [];
15664     }
15665 };
15666
15667 Ext.CompositeElementLite.prototype.on = Ext.CompositeElementLite.prototype.addListener;
15668
15669 /**
15670  * @private
15671  * Copies all of the functions from Ext.core.Element's prototype onto CompositeElementLite's prototype.
15672  * This is called twice - once immediately below, and once again after additional Ext.core.Element
15673  * are added in Ext JS
15674  */
15675 Ext.CompositeElementLite.importElementMethods = function() {
15676     var fnName,
15677         ElProto = Ext.core.Element.prototype,
15678         CelProto = Ext.CompositeElementLite.prototype;
15679
15680     for (fnName in ElProto) {
15681         if (typeof ElProto[fnName] == 'function'){
15682             (function(fnName) {
15683                 CelProto[fnName] = CelProto[fnName] || function() {
15684                     return this.invoke(fnName, arguments);
15685                 };
15686             }).call(CelProto, fnName);
15687
15688         }
15689     }
15690 };
15691
15692 Ext.CompositeElementLite.importElementMethods();
15693
15694 if(Ext.DomQuery){
15695     Ext.core.Element.selectorFunction = Ext.DomQuery.select;
15696 }
15697
15698 /**
15699  * Selects elements based on the passed CSS selector to enable {@link Ext.core.Element Element} methods
15700  * to be applied to many related elements in one statement through the returned {@link Ext.CompositeElement CompositeElement} or
15701  * {@link Ext.CompositeElementLite CompositeElementLite} object.
15702  * @param {String/Array} selector The CSS selector or an array of elements
15703  * @param {HTMLElement/String} root (optional) The root element of the query or id of the root
15704  * @return {CompositeElementLite/CompositeElement}
15705  * @member Ext.core.Element
15706  * @method select
15707  */
15708 Ext.core.Element.select = function(selector, root){
15709     var els;
15710     if(typeof selector == "string"){
15711         els = Ext.core.Element.selectorFunction(selector, root);
15712     }else if(selector.length !== undefined){
15713         els = selector;
15714     }else{
15715         Ext.Error.raise({
15716             sourceClass: "Ext.core.Element",
15717             sourceMethod: "select",
15718             selector: selector,
15719             root: root,
15720             msg: "Invalid selector specified: " + selector
15721         });
15722     }
15723     return new Ext.CompositeElementLite(els);
15724 };
15725 /**
15726  * Selects elements based on the passed CSS selector to enable {@link Ext.core.Element Element} methods
15727  * to be applied to many related elements in one statement through the returned {@link Ext.CompositeElement CompositeElement} or
15728  * {@link Ext.CompositeElementLite CompositeElementLite} object.
15729  * @param {String/Array} selector The CSS selector or an array of elements
15730  * @param {HTMLElement/String} root (optional) The root element of the query or id of the root
15731  * @return {CompositeElementLite/CompositeElement}
15732  * @member Ext
15733  * @method select
15734  */
15735 Ext.select = Ext.core.Element.select;
15736
15737 /**
15738  * @class Ext.util.DelayedTask
15739  * 
15740  * The DelayedTask class provides a convenient way to "buffer" the execution of a method,
15741  * performing setTimeout where a new timeout cancels the old timeout. When called, the
15742  * task will wait the specified time period before executing. If durng that time period,
15743  * the task is called again, the original call will be cancelled. This continues so that
15744  * the function is only called a single time for each iteration.
15745  * 
15746  * This method is especially useful for things like detecting whether a user has finished
15747  * typing in a text field. An example would be performing validation on a keypress. You can
15748  * use this class to buffer the keypress events for a certain number of milliseconds, and
15749  * perform only if they stop for that amount of time.  
15750  * 
15751  * ## Usage
15752  * 
15753  *     var task = new Ext.util.DelayedTask(function(){
15754  *         alert(Ext.getDom('myInputField').value.length);
15755  *     });
15756  *     
15757  *     // Wait 500ms before calling our function. If the user presses another key
15758  *     // during that 500ms, it will be cancelled and we'll wait another 500ms.
15759  *     Ext.get('myInputField').on('keypress', function(){
15760  *         task.{@link #delay}(500);
15761  *     });
15762  * 
15763  * Note that we are using a DelayedTask here to illustrate a point. The configuration
15764  * option `buffer` for {@link Ext.util.Observable#addListener addListener/on} will
15765  * also setup a delayed task for you to buffer events.
15766  * 
15767  * @constructor The parameters to this constructor serve as defaults and are not required.
15768  * @param {Function} fn (optional) The default function to call.
15769  * @param {Object} scope The default scope (The <code><b>this</b></code> reference) in which the
15770  * function is called. If not specified, <code>this</code> will refer to the browser window.
15771  * @param {Array} args (optional) The default Array of arguments.
15772  */
15773 Ext.util.DelayedTask = function(fn, scope, args) {
15774     var me = this,
15775         id,
15776         call = function() {
15777             clearInterval(id);
15778             id = null;
15779             fn.apply(scope, args || []);
15780         };
15781
15782     /**
15783      * Cancels any pending timeout and queues a new one
15784      * @param {Number} delay The milliseconds to delay
15785      * @param {Function} newFn (optional) Overrides function passed to constructor
15786      * @param {Object} newScope (optional) Overrides scope passed to constructor. Remember that if no scope
15787      * is specified, <code>this</code> will refer to the browser window.
15788      * @param {Array} newArgs (optional) Overrides args passed to constructor
15789      */
15790     this.delay = function(delay, newFn, newScope, newArgs) {
15791         me.cancel();
15792         fn = newFn || fn;
15793         scope = newScope || scope;
15794         args = newArgs || args;
15795         id = setInterval(call, delay);
15796     };
15797
15798     /**
15799      * Cancel the last queued timeout
15800      */
15801     this.cancel = function(){
15802         if (id) {
15803             clearInterval(id);
15804             id = null;
15805         }
15806     };
15807 };
15808 Ext.require('Ext.util.DelayedTask', function() {
15809
15810     Ext.util.Event = Ext.extend(Object, (function() {
15811         function createBuffered(handler, listener, o, scope) {
15812             listener.task = new Ext.util.DelayedTask();
15813             return function() {
15814                 listener.task.delay(o.buffer, handler, scope, Ext.Array.toArray(arguments));
15815             };
15816         }
15817
15818         function createDelayed(handler, listener, o, scope) {
15819             return function() {
15820                 var task = new Ext.util.DelayedTask();
15821                 if (!listener.tasks) {
15822                     listener.tasks = [];
15823                 }
15824                 listener.tasks.push(task);
15825                 task.delay(o.delay || 10, handler, scope, Ext.Array.toArray(arguments));
15826             };
15827         }
15828
15829         function createSingle(handler, listener, o, scope) {
15830             return function() {
15831                 listener.ev.removeListener(listener.fn, scope);
15832                 return handler.apply(scope, arguments);
15833             };
15834         }
15835
15836         return {
15837             isEvent: true,
15838
15839             constructor: function(observable, name) {
15840                 this.name = name;
15841                 this.observable = observable;
15842                 this.listeners = [];
15843             },
15844
15845             addListener: function(fn, scope, options) {
15846                 var me = this,
15847                     listener;
15848                     scope = scope || me.observable;
15849
15850                 if (!fn) {
15851                     Ext.Error.raise({
15852                         sourceClass: Ext.getClassName(this.observable),
15853                         sourceMethod: "addListener",
15854                         msg: "The specified callback function is undefined"
15855                     });
15856                 }
15857
15858                 if (!me.isListening(fn, scope)) {
15859                     listener = me.createListener(fn, scope, options);
15860                     if (me.firing) {
15861                         // if we are currently firing this event, don't disturb the listener loop
15862                         me.listeners = me.listeners.slice(0);
15863                     }
15864                     me.listeners.push(listener);
15865                 }
15866             },
15867
15868             createListener: function(fn, scope, o) {
15869                 o = o || {};
15870                 scope = scope || this.observable;
15871
15872                 var listener = {
15873                         fn: fn,
15874                         scope: scope,
15875                         o: o,
15876                         ev: this
15877                     },
15878                     handler = fn;
15879
15880                 // The order is important. The 'single' wrapper must be wrapped by the 'buffer' and 'delayed' wrapper
15881                 // because the event removal that the single listener does destroys the listener's DelayedTask(s)
15882                 if (o.single) {
15883                     handler = createSingle(handler, listener, o, scope);
15884                 }
15885                 if (o.delay) {
15886                     handler = createDelayed(handler, listener, o, scope);
15887                 }
15888                 if (o.buffer) {
15889                     handler = createBuffered(handler, listener, o, scope);
15890                 }
15891
15892                 listener.fireFn = handler;
15893                 return listener;
15894             },
15895
15896             findListener: function(fn, scope) {
15897                 var listeners = this.listeners,
15898                 i = listeners.length,
15899                 listener,
15900                 s;
15901
15902                 while (i--) {
15903                     listener = listeners[i];
15904                     if (listener) {
15905                         s = listener.scope;
15906                         if (listener.fn == fn && (s == scope || s == this.observable)) {
15907                             return i;
15908                         }
15909                     }
15910                 }
15911
15912                 return - 1;
15913             },
15914
15915             isListening: function(fn, scope) {
15916                 return this.findListener(fn, scope) !== -1;
15917             },
15918
15919             removeListener: function(fn, scope) {
15920                 var me = this,
15921                     index,
15922                     listener,
15923                     k;
15924                 index = me.findListener(fn, scope);
15925                 if (index != -1) {
15926                     listener = me.listeners[index];
15927
15928                     if (me.firing) {
15929                         me.listeners = me.listeners.slice(0);
15930                     }
15931
15932                     // cancel and remove a buffered handler that hasn't fired yet
15933                     if (listener.task) {
15934                         listener.task.cancel();
15935                         delete listener.task;
15936                     }
15937
15938                     // cancel and remove all delayed handlers that haven't fired yet
15939                     k = listener.tasks && listener.tasks.length;
15940                     if (k) {
15941                         while (k--) {
15942                             listener.tasks[k].cancel();
15943                         }
15944                         delete listener.tasks;
15945                     }
15946
15947                     // remove this listener from the listeners array
15948                     me.listeners.splice(index, 1);
15949                     return true;
15950                 }
15951
15952                 return false;
15953             },
15954
15955             // Iterate to stop any buffered/delayed events
15956             clearListeners: function() {
15957                 var listeners = this.listeners,
15958                     i = listeners.length;
15959
15960                 while (i--) {
15961                     this.removeListener(listeners[i].fn, listeners[i].scope);
15962                 }
15963             },
15964
15965             fire: function() {
15966                 var me = this,
15967                     listeners = me.listeners,
15968                     count = listeners.length,
15969                     i,
15970                     args,
15971                     listener;
15972
15973                 if (count > 0) {
15974                     me.firing = true;
15975                     for (i = 0; i < count; i++) {
15976                         listener = listeners[i];
15977                         args = arguments.length ? Array.prototype.slice.call(arguments, 0) : [];
15978                         if (listener.o) {
15979                             args.push(listener.o);
15980                         }
15981                         if (listener && listener.fireFn.apply(listener.scope || me.observable, args) === false) {
15982                             return (me.firing = false);
15983                         }
15984                     }
15985                 }
15986                 me.firing = false;
15987                 return true;
15988             }
15989         };
15990     })());
15991 });
15992
15993 /**
15994  * @class Ext.EventManager
15995  * Registers event handlers that want to receive a normalized EventObject instead of the standard browser event and provides
15996  * several useful events directly.
15997  * See {@link Ext.EventObject} for more details on normalized event objects.
15998  * @singleton
15999  */
16000 Ext.EventManager = {
16001
16002     // --------------------- onReady ---------------------
16003
16004     /**
16005      * Check if we have bound our global onReady listener
16006      * @private
16007      */
16008     hasBoundOnReady: false,
16009
16010     /**
16011      * Check if fireDocReady has been called
16012      * @private
16013      */
16014     hasFiredReady: false,
16015
16016     /**
16017      * Timer for the document ready event in old IE versions
16018      * @private
16019      */
16020     readyTimeout: null,
16021
16022     /**
16023      * Checks if we have bound an onreadystatechange event
16024      * @private
16025      */
16026     hasOnReadyStateChange: false,
16027
16028     /**
16029      * Holds references to any onReady functions
16030      * @private
16031      */
16032     readyEvent: new Ext.util.Event(),
16033
16034     /**
16035      * Check the ready state for old IE versions
16036      * @private
16037      * @return {Boolean} True if the document is ready
16038      */
16039     checkReadyState: function(){
16040         var me = Ext.EventManager;
16041
16042         if(window.attachEvent){
16043             // See here for reference: http://javascript.nwbox.com/IEContentLoaded/
16044             if (window != top) {
16045                 return false;
16046             }
16047             try{
16048                 document.documentElement.doScroll('left');
16049             }catch(e){
16050                 return false;
16051             }
16052             me.fireDocReady();
16053             return true;
16054         }
16055         if (document.readyState == 'complete') {
16056             me.fireDocReady();
16057             return true;
16058         }
16059         me.readyTimeout = setTimeout(arguments.callee, 2);
16060         return false;
16061     },
16062
16063     /**
16064      * Binds the appropriate browser event for checking if the DOM has loaded.
16065      * @private
16066      */
16067     bindReadyEvent: function(){
16068         var me = Ext.EventManager;
16069         if (me.hasBoundOnReady) {
16070             return;
16071         }
16072
16073         if (document.addEventListener) {
16074             document.addEventListener('DOMContentLoaded', me.fireDocReady, false);
16075             // fallback, load will ~always~ fire
16076             window.addEventListener('load', me.fireDocReady, false);
16077         } else {
16078             // check if the document is ready, this will also kick off the scroll checking timer
16079             if (!me.checkReadyState()) {
16080                 document.attachEvent('onreadystatechange', me.checkReadyState);
16081                 me.hasOnReadyStateChange = true;
16082             }
16083             // fallback, onload will ~always~ fire
16084             window.attachEvent('onload', me.fireDocReady, false);
16085         }
16086         me.hasBoundOnReady = true;
16087     },
16088
16089     /**
16090      * We know the document is loaded, so trigger any onReady events.
16091      * @private
16092      */
16093     fireDocReady: function(){
16094         var me = Ext.EventManager;
16095
16096         // only unbind these events once
16097         if (!me.hasFiredReady) {
16098             me.hasFiredReady = true;
16099
16100             if (document.addEventListener) {
16101                 document.removeEventListener('DOMContentLoaded', me.fireDocReady, false);
16102                 window.removeEventListener('load', me.fireDocReady, false);
16103             } else {
16104                 if (me.readyTimeout !== null) {
16105                     clearTimeout(me.readyTimeout);
16106                 }
16107                 if (me.hasOnReadyStateChange) {
16108                     document.detachEvent('onreadystatechange', me.checkReadyState);
16109                 }
16110                 window.detachEvent('onload', me.fireDocReady);
16111             }
16112             Ext.supports.init();
16113         }
16114         if (!Ext.isReady) {
16115             Ext.isReady = true;
16116             me.onWindowUnload();
16117             me.readyEvent.fire();
16118         }
16119     },
16120
16121     /**
16122      * Adds a listener to be notified when the document is ready (before onload and before images are loaded). Can be
16123      * accessed shorthanded as Ext.onReady().
16124      * @param {Function} fn The method the event invokes.
16125      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the handler function executes. Defaults to the browser window.
16126      * @param {boolean} options (optional) Options object as passed to {@link Ext.core.Element#addListener}.
16127      */
16128     onDocumentReady: function(fn, scope, options){
16129         options = options || {};
16130         var me = Ext.EventManager,
16131             readyEvent = me.readyEvent;
16132
16133         // force single to be true so our event is only ever fired once.
16134         options.single = true;
16135
16136         // Document already loaded, let's just fire it
16137         if (Ext.isReady) {
16138             readyEvent.addListener(fn, scope, options);
16139             readyEvent.fire();
16140         } else {
16141             options.delay = options.delay || 1;
16142             readyEvent.addListener(fn, scope, options);
16143             me.bindReadyEvent();
16144         }
16145     },
16146
16147
16148     // --------------------- event binding ---------------------
16149
16150     /**
16151      * Contains a list of all document mouse downs, so we can ensure they fire even when stopEvent is called.
16152      * @private
16153      */
16154     stoppedMouseDownEvent: new Ext.util.Event(),
16155
16156     /**
16157      * Options to parse for the 4th argument to addListener.
16158      * @private
16159      */
16160     propRe: /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate|freezeEvent)$/,
16161
16162     /**
16163      * Get the id of the element. If one has not been assigned, automatically assign it.
16164      * @param {Mixed} element The element to get the id for.
16165      * @return {String} id
16166      */
16167     getId : function(element) {
16168         var skipGarbageCollection = false,
16169             id;
16170     
16171         element = Ext.getDom(element);
16172     
16173         if (element === document || element === window) {
16174             id = element === document ? Ext.documentId : Ext.windowId;
16175         }
16176         else {
16177             id = Ext.id(element);
16178         }
16179         // skip garbage collection for special elements (window, document, iframes)
16180         if (element && (element.getElementById || element.navigator)) {
16181             skipGarbageCollection = true;
16182         }
16183     
16184         if (!Ext.cache[id]){
16185             Ext.core.Element.addToCache(new Ext.core.Element(element), id);
16186             if (skipGarbageCollection) {
16187                 Ext.cache[id].skipGarbageCollection = true;
16188             }
16189         }
16190         return id;
16191     },
16192
16193     /**
16194      * Convert a "config style" listener into a set of flat arguments so they can be passed to addListener
16195      * @private
16196      * @param {Object} element The element the event is for
16197      * @param {Object} event The event configuration
16198      * @param {Object} isRemove True if a removal should be performed, otherwise an add will be done.
16199      */
16200     prepareListenerConfig: function(element, config, isRemove){
16201         var me = this,
16202             propRe = me.propRe,
16203             key, value, args;
16204
16205         // loop over all the keys in the object
16206         for (key in config) {
16207             if (config.hasOwnProperty(key)) {
16208                 // if the key is something else then an event option
16209                 if (!propRe.test(key)) {
16210                     value = config[key];
16211                     // if the value is a function it must be something like click: function(){}, scope: this
16212                     // which means that there might be multiple event listeners with shared options
16213                     if (Ext.isFunction(value)) {
16214                         // shared options
16215                         args = [element, key, value, config.scope, config];
16216                     } else {
16217                         // if its not a function, it must be an object like click: {fn: function(){}, scope: this}
16218                         args = [element, key, value.fn, value.scope, value];
16219                     }
16220
16221                     if (isRemove === true) {
16222                         me.removeListener.apply(this, args);
16223                     } else {
16224                         me.addListener.apply(me, args);
16225                     }
16226                 }
16227             }
16228         }
16229     },
16230
16231     /**
16232      * Normalize cross browser event differences
16233      * @private
16234      * @param {Object} eventName The event name
16235      * @param {Object} fn The function to execute
16236      * @return {Object} The new event name/function
16237      */
16238     normalizeEvent: function(eventName, fn){
16239         if (/mouseenter|mouseleave/.test(eventName) && !Ext.supports.MouseEnterLeave) {
16240             if (fn) {
16241                 fn = Ext.Function.createInterceptor(fn, this.contains, this);
16242             }
16243             eventName = eventName == 'mouseenter' ? 'mouseover' : 'mouseout';
16244         } else if (eventName == 'mousewheel' && !Ext.supports.MouseWheel && !Ext.isOpera){
16245             eventName = 'DOMMouseScroll';
16246         }
16247         return {
16248             eventName: eventName,
16249             fn: fn
16250         };
16251     },
16252
16253     /**
16254      * Checks whether the event's relatedTarget is contained inside (or <b>is</b>) the element.
16255      * @private
16256      * @param {Object} event
16257      */
16258     contains: function(event){
16259         var parent = event.browserEvent.currentTarget,
16260             child = this.getRelatedTarget(event);
16261
16262         if (parent && parent.firstChild) {
16263             while (child) {
16264                 if (child === parent) {
16265                     return false;
16266                 }
16267                 child = child.parentNode;
16268                 if (child && (child.nodeType != 1)) {
16269                     child = null;
16270                 }
16271             }
16272         }
16273         return true;
16274     },
16275
16276     /**
16277     * Appends an event handler to an element.  The shorthand version {@link #on} is equivalent.  Typically you will
16278     * use {@link Ext.core.Element#addListener} directly on an Element in favor of calling this version.
16279     * @param {String/HTMLElement} el The html element or id to assign the event handler to.
16280     * @param {String} eventName The name of the event to listen for.
16281     * @param {Function} handler The handler function the event invokes. This function is passed
16282     * the following parameters:<ul>
16283     * <li>evt : EventObject<div class="sub-desc">The {@link Ext.EventObject EventObject} describing the event.</div></li>
16284     * <li>t : Element<div class="sub-desc">The {@link Ext.core.Element Element} which was the target of the event.
16285     * Note that this may be filtered by using the <tt>delegate</tt> option.</div></li>
16286     * <li>o : Object<div class="sub-desc">The options object from the addListener call.</div></li>
16287     * </ul>
16288     * @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>.
16289     * @param {Object} options (optional) An object containing handler configuration properties.
16290     * This may contain any of the following properties:<ul>
16291     * <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>
16292     * <li>delegate : String<div class="sub-desc">A simple selector to filter the target or look for a descendant of the target</div></li>
16293     * <li>stopEvent : Boolean<div class="sub-desc">True to stop the event. That is stop propagation, and prevent the default action.</div></li>
16294     * <li>preventDefault : Boolean<div class="sub-desc">True to prevent the default action</div></li>
16295     * <li>stopPropagation : Boolean<div class="sub-desc">True to prevent event propagation</div></li>
16296     * <li>normalized : Boolean<div class="sub-desc">False to pass a browser event to the handler function instead of an Ext.EventObject</div></li>
16297     * <li>delay : Number<div class="sub-desc">The number of milliseconds to delay the invocation of the handler after te event fires.</div></li>
16298     * <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>
16299     * <li>buffer : Number<div class="sub-desc">Causes the handler to be scheduled to run in an {@link Ext.util.DelayedTask} delayed
16300     * by the specified number of milliseconds. If the event fires again within that time, the original
16301     * handler is <em>not</em> invoked, but the new handler is scheduled in its place.</div></li>
16302     * <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>
16303     * </ul><br>
16304     * <p>See {@link Ext.core.Element#addListener} for examples of how to use these options.</p>
16305     */
16306     addListener: function(element, eventName, fn, scope, options){
16307         // Check if we've been passed a "config style" event.
16308         if (Ext.isObject(eventName)) {
16309             this.prepareListenerConfig(element, eventName);
16310             return;
16311         }
16312
16313         var dom = Ext.getDom(element),
16314             bind,
16315             wrap;
16316
16317         if (!dom){
16318             Ext.Error.raise({
16319                 sourceClass: 'Ext.EventManager',
16320                 sourceMethod: 'addListener',
16321                 targetElement: element,
16322                 eventName: eventName,
16323                 msg: 'Error adding "' + eventName + '\" listener for nonexistent element "' + element + '"'
16324             });
16325         }
16326         if (!fn) {
16327             Ext.Error.raise({
16328                 sourceClass: 'Ext.EventManager',
16329                 sourceMethod: 'addListener',
16330                 targetElement: element,
16331                 eventName: eventName,
16332                 msg: 'Error adding "' + eventName + '\" listener. The handler function is undefined.'
16333             });
16334         }
16335
16336         // create the wrapper function
16337         options = options || {};
16338
16339         bind = this.normalizeEvent(eventName, fn);
16340         wrap = this.createListenerWrap(dom, eventName, bind.fn, scope, options);
16341
16342
16343         if (dom.attachEvent) {
16344             dom.attachEvent('on' + bind.eventName, wrap);
16345         } else {
16346             dom.addEventListener(bind.eventName, wrap, options.capture || false);
16347         }
16348
16349         if (dom == document && eventName == 'mousedown') {
16350             this.stoppedMouseDownEvent.addListener(wrap);
16351         }
16352
16353         // add all required data into the event cache
16354         this.getEventListenerCache(dom, eventName).push({
16355             fn: fn,
16356             wrap: wrap,
16357             scope: scope
16358         });
16359     },
16360
16361     /**
16362     * Removes an event handler from an element.  The shorthand version {@link #un} is equivalent.  Typically
16363     * you will use {@link Ext.core.Element#removeListener} directly on an Element in favor of calling this version.
16364     * @param {String/HTMLElement} el The id or html element from which to remove the listener.
16365     * @param {String} eventName The name of the event.
16366     * @param {Function} fn The handler function to remove. <b>This must be a reference to the function passed into the {@link #addListener} call.</b>
16367     * @param {Object} scope If a scope (<b><code>this</code></b> reference) was specified when the listener was added,
16368     * then this must refer to the same object.
16369     */
16370     removeListener : function(element, eventName, fn, scope) {
16371         // handle our listener config object syntax
16372         if (Ext.isObject(eventName)) {
16373             this.prepareListenerConfig(element, eventName, true);
16374             return;
16375         }
16376
16377         var dom = Ext.getDom(element),
16378             cache = this.getEventListenerCache(dom, eventName),
16379             bindName = this.normalizeEvent(eventName).eventName,
16380             i = cache.length, j,
16381             listener, wrap, tasks;
16382
16383
16384         while (i--) {
16385             listener = cache[i];
16386
16387             if (listener && (!fn || listener.fn == fn) && (!scope || listener.scope === scope)) {
16388                 wrap = listener.wrap;
16389
16390                 // clear buffered calls
16391                 if (wrap.task) {
16392                     clearTimeout(wrap.task);
16393                     delete wrap.task;
16394                 }
16395
16396                 // clear delayed calls
16397                 j = wrap.tasks && wrap.tasks.length;
16398                 if (j) {
16399                     while (j--) {
16400                         clearTimeout(wrap.tasks[j]);
16401                     }
16402                     delete wrap.tasks;
16403                 }
16404
16405                 if (dom.detachEvent) {
16406                     dom.detachEvent('on' + bindName, wrap);
16407                 } else {
16408                     dom.removeEventListener(bindName, wrap, false);
16409                 }
16410
16411                 if (wrap && dom == document && eventName == 'mousedown') {
16412                     this.stoppedMouseDownEvent.removeListener(wrap);
16413                 }
16414
16415                 // remove listener from cache
16416                 cache.splice(i, 1);
16417             }
16418         }
16419     },
16420
16421     /**
16422     * Removes all event handers from an element.  Typically you will use {@link Ext.core.Element#removeAllListeners}
16423     * directly on an Element in favor of calling this version.
16424     * @param {String/HTMLElement} el The id or html element from which to remove all event handlers.
16425     */
16426     removeAll : function(element){
16427         var dom = Ext.getDom(element),
16428             cache, ev;
16429         if (!dom) {
16430             return;
16431         }
16432         cache = this.getElementEventCache(dom);
16433
16434         for (ev in cache) {
16435             if (cache.hasOwnProperty(ev)) {
16436                 this.removeListener(dom, ev);
16437             }
16438         }
16439         Ext.cache[dom.id].events = {};
16440     },
16441
16442     /**
16443      * Recursively removes all previous added listeners from an element and its children. Typically you will use {@link Ext.core.Element#purgeAllListeners}
16444      * directly on an Element in favor of calling this version.
16445      * @param {String/HTMLElement} el The id or html element from which to remove all event handlers.
16446      * @param {String} eventName (optional) The name of the event.
16447      */
16448     purgeElement : function(element, eventName) {
16449         var dom = Ext.getDom(element),
16450             i = 0, len;
16451
16452         if(eventName) {
16453             this.removeListener(dom, eventName);
16454         }
16455         else {
16456             this.removeAll(dom);
16457         }
16458
16459         if(dom && dom.childNodes) {
16460             for(len = element.childNodes.length; i < len; i++) {
16461                 this.purgeElement(element.childNodes[i], eventName);
16462             }
16463         }
16464     },
16465
16466     /**
16467      * Create the wrapper function for the event
16468      * @private
16469      * @param {HTMLElement} dom The dom element
16470      * @param {String} ename The event name
16471      * @param {Function} fn The function to execute
16472      * @param {Object} scope The scope to execute callback in
16473      * @param {Object} o The options
16474      */
16475     createListenerWrap : function(dom, ename, fn, scope, options) {
16476         options = !Ext.isObject(options) ? {} : options;
16477
16478         var f = ['if(!Ext) {return;}'],
16479             gen;
16480
16481         if(options.buffer || options.delay || options.freezeEvent) {
16482             f.push('e = new Ext.EventObjectImpl(e, ' + (options.freezeEvent ? 'true' : 'false' ) + ');');
16483         } else {
16484             f.push('e = Ext.EventObject.setEvent(e);');
16485         }
16486
16487         if (options.delegate) {
16488             f.push('var t = e.getTarget("' + options.delegate + '", this);');
16489             f.push('if(!t) {return;}');
16490         } else {
16491             f.push('var t = e.target;');
16492         }
16493
16494         if (options.target) {
16495             f.push('if(e.target !== options.target) {return;}');
16496         }
16497
16498         if(options.stopEvent) {
16499             f.push('e.stopEvent();');
16500         } else {
16501             if(options.preventDefault) {
16502                 f.push('e.preventDefault();');
16503             }
16504             if(options.stopPropagation) {
16505                 f.push('e.stopPropagation();');
16506             }
16507         }
16508
16509         if(options.normalized === false) {
16510             f.push('e = e.browserEvent;');
16511         }
16512
16513         if(options.buffer) {
16514             f.push('(wrap.task && clearTimeout(wrap.task));');
16515             f.push('wrap.task = setTimeout(function(){');
16516         }
16517
16518         if(options.delay) {
16519             f.push('wrap.tasks = wrap.tasks || [];');
16520             f.push('wrap.tasks.push(setTimeout(function(){');
16521         }
16522
16523         // finally call the actual handler fn
16524         f.push('fn.call(scope || dom, e, t, options);');
16525
16526         if(options.single) {
16527             f.push('Ext.EventManager.removeListener(dom, ename, fn, scope);');
16528         }
16529
16530         if(options.delay) {
16531             f.push('}, ' + options.delay + '));');
16532         }
16533
16534         if(options.buffer) {
16535             f.push('}, ' + options.buffer + ');');
16536         }
16537
16538         gen = Ext.functionFactory('e', 'options', 'fn', 'scope', 'ename', 'dom', 'wrap', 'args', f.join('\n'));
16539
16540         return function wrap(e, args) {
16541             gen.call(dom, e, options, fn, scope, ename, dom, wrap, args);
16542         };
16543     },
16544
16545     /**
16546      * Get the event cache for a particular element for a particular event
16547      * @private
16548      * @param {HTMLElement} element The element
16549      * @param {Object} eventName The event name
16550      * @return {Array} The events for the element
16551      */
16552     getEventListenerCache : function(element, eventName) {
16553         var eventCache = this.getElementEventCache(element);
16554         return eventCache[eventName] || (eventCache[eventName] = []);
16555     },
16556
16557     /**
16558      * Gets the event cache for the object
16559      * @private
16560      * @param {HTMLElement} element The element
16561      * @return {Object} The event cache for the object
16562      */
16563     getElementEventCache : function(element) {
16564         var elementCache = Ext.cache[this.getId(element)];
16565         return elementCache.events || (elementCache.events = {});
16566     },
16567
16568     // --------------------- utility methods ---------------------
16569     mouseLeaveRe: /(mouseout|mouseleave)/,
16570     mouseEnterRe: /(mouseover|mouseenter)/,
16571
16572     /**
16573      * Stop the event (preventDefault and stopPropagation)
16574      * @param {Event} The event to stop
16575      */
16576     stopEvent: function(event) {
16577         this.stopPropagation(event);
16578         this.preventDefault(event);
16579     },
16580
16581     /**
16582      * Cancels bubbling of the event.
16583      * @param {Event} The event to stop bubbling.
16584      */
16585     stopPropagation: function(event) {
16586         event = event.browserEvent || event;
16587         if (event.stopPropagation) {
16588             event.stopPropagation();
16589         } else {
16590             event.cancelBubble = true;
16591         }
16592     },
16593
16594     /**
16595      * Prevents the browsers default handling of the event.
16596      * @param {Event} The event to prevent the default
16597      */
16598     preventDefault: function(event) {
16599         event = event.browserEvent || event;
16600         if (event.preventDefault) {
16601             event.preventDefault();
16602         } else {
16603             event.returnValue = false;
16604             // Some keys events require setting the keyCode to -1 to be prevented
16605             try {
16606               // all ctrl + X and F1 -> F12
16607               if (event.ctrlKey || event.keyCode > 111 && event.keyCode < 124) {
16608                   event.keyCode = -1;
16609               }
16610             } catch (e) {
16611                 // see this outdated document http://support.microsoft.com/kb/934364/en-us for more info
16612             }
16613         }
16614     },
16615
16616     /**
16617      * Gets the related target from the event.
16618      * @param {Object} event The event
16619      * @return {HTMLElement} The related target.
16620      */
16621     getRelatedTarget: function(event) {
16622         event = event.browserEvent || event;
16623         var target = event.relatedTarget;
16624         if (!target) {
16625             if (this.mouseLeaveRe.test(event.type)) {
16626                 target = event.toElement;
16627             } else if (this.mouseEnterRe.test(event.type)) {
16628                 target = event.fromElement;
16629             }
16630         }
16631         return this.resolveTextNode(target);
16632     },
16633
16634     /**
16635      * Gets the x coordinate from the event
16636      * @param {Object} event The event
16637      * @return {Number} The x coordinate
16638      */
16639     getPageX: function(event) {
16640         return this.getXY(event)[0];
16641     },
16642
16643     /**
16644      * Gets the y coordinate from the event
16645      * @param {Object} event The event
16646      * @return {Number} The y coordinate
16647      */
16648     getPageY: function(event) {
16649         return this.getXY(event)[1];
16650     },
16651
16652     /**
16653      * Gets the x & ycoordinate from the event
16654      * @param {Object} event The event
16655      * @return {Array} The x/y coordinate
16656      */
16657     getPageXY: function(event) {
16658         event = event.browserEvent || event;
16659         var x = event.pageX,
16660             y = event.pageY,
16661             doc = document.documentElement,
16662             body = document.body;
16663
16664         // pageX/pageY not available (undefined, not null), use clientX/clientY instead
16665         if (!x && x !== 0) {
16666             x = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
16667             y = event.clientY + (doc && doc.scrollTop  || body && body.scrollTop  || 0) - (doc && doc.clientTop  || body && body.clientTop  || 0);
16668         }
16669         return [x, y];
16670     },
16671
16672     /**
16673      * Gets the target of the event.
16674      * @param {Object} event The event
16675      * @return {HTMLElement} target
16676      */
16677     getTarget: function(event) {
16678         event = event.browserEvent || event;
16679         return this.resolveTextNode(event.target || event.srcElement);
16680     },
16681
16682     /**
16683      * Resolve any text nodes accounting for browser differences.
16684      * @private
16685      * @param {HTMLElement} node The node
16686      * @return {HTMLElement} The resolved node
16687      */
16688     // 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.
16689     resolveTextNode: Ext.isGecko ?
16690         function(node) {
16691             if (!node) {
16692                 return;
16693             }
16694             // work around firefox bug, https://bugzilla.mozilla.org/show_bug.cgi?id=101197
16695             var s = HTMLElement.prototype.toString.call(node);
16696             if (s == '[xpconnect wrapped native prototype]' || s == '[object XULElement]') {
16697                 return;
16698             }
16699                 return node.nodeType == 3 ? node.parentNode: node;
16700             }: function(node) {
16701                 return node && node.nodeType == 3 ? node.parentNode: node;
16702             },
16703
16704     // --------------------- custom event binding ---------------------
16705
16706     // Keep track of the current width/height
16707     curWidth: 0,
16708     curHeight: 0,
16709
16710     /**
16711      * Adds a listener to be notified when the browser window is resized and provides resize event buffering (100 milliseconds),
16712      * passes new viewport width and height to handlers.
16713      * @param {Function} fn      The handler function the window resize event invokes.
16714      * @param {Object}   scope   The scope (<code>this</code> reference) in which the handler function executes. Defaults to the browser window.
16715      * @param {boolean}  options Options object as passed to {@link Ext.core.Element#addListener}
16716      */
16717     onWindowResize: function(fn, scope, options){
16718         var resize = this.resizeEvent;
16719         if(!resize){
16720             this.resizeEvent = resize = new Ext.util.Event();
16721             this.on(window, 'resize', this.fireResize, this, {buffer: 100});
16722         }
16723         resize.addListener(fn, scope, options);
16724     },
16725
16726     /**
16727      * Fire the resize event.
16728      * @private
16729      */
16730     fireResize: function(){
16731         var me = this,
16732             w = Ext.core.Element.getViewWidth(),
16733             h = Ext.core.Element.getViewHeight();
16734
16735          //whacky problem in IE where the resize event will sometimes fire even though the w/h are the same.
16736          if(me.curHeight != h || me.curWidth != w){
16737              me.curHeight = h;
16738              me.curWidth = w;
16739              me.resizeEvent.fire(w, h);
16740          }
16741     },
16742
16743     /**
16744      * Removes the passed window resize listener.
16745      * @param {Function} fn        The method the event invokes
16746      * @param {Object}   scope    The scope of handler
16747      */
16748     removeResizeListener: function(fn, scope){
16749         if (this.resizeEvent) {
16750             this.resizeEvent.removeListener(fn, scope);
16751         }
16752     },
16753
16754     onWindowUnload: function() {
16755         var unload = this.unloadEvent;
16756         if (!unload) {
16757             this.unloadEvent = unload = new Ext.util.Event();
16758             this.addListener(window, 'unload', this.fireUnload, this);
16759         }
16760     },
16761
16762     /**
16763      * Fires the unload event for items bound with onWindowUnload
16764      * @private
16765      */
16766     fireUnload: function() {
16767         // wrap in a try catch, could have some problems during unload
16768         try {
16769             this.removeUnloadListener();
16770             // Work around FF3 remembering the last scroll position when refreshing the grid and then losing grid view
16771             if (Ext.isGecko3) {
16772                 var gridviews = Ext.ComponentQuery.query('gridview'),
16773                     i = 0,
16774                     ln = gridviews.length;
16775                 for (; i < ln; i++) {
16776                     gridviews[i].scrollToTop();
16777                 }
16778             }
16779             // Purge all elements in the cache
16780             var el,
16781                 cache = Ext.cache;
16782             for (el in cache) {
16783                 if (cache.hasOwnProperty(el)) {
16784                     Ext.EventManager.removeAll(el);
16785                 }
16786             }
16787         } catch(e) {
16788         }
16789     },
16790
16791     /**
16792      * Removes the passed window unload listener.
16793      * @param {Function} fn        The method the event invokes
16794      * @param {Object}   scope    The scope of handler
16795      */
16796     removeUnloadListener: function(){
16797         if (this.unloadEvent) {
16798             this.removeListener(window, 'unload', this.fireUnload);
16799         }
16800     },
16801
16802     /**
16803      * note 1: IE fires ONLY the keydown event on specialkey autorepeat
16804      * note 2: Safari < 3.1, Gecko (Mac/Linux) & Opera fire only the keypress event on specialkey autorepeat
16805      * (research done by @Jan Wolter at http://unixpapa.com/js/key.html)
16806      * @private
16807      */
16808     useKeyDown: Ext.isWebKit ?
16809                    parseInt(navigator.userAgent.match(/AppleWebKit\/(\d+)/)[1], 10) >= 525 :
16810                    !((Ext.isGecko && !Ext.isWindows) || Ext.isOpera),
16811
16812     /**
16813      * Indicates which event to use for getting key presses.
16814      * @return {String} The appropriate event name.
16815      */
16816     getKeyEvent: function(){
16817         return this.useKeyDown ? 'keydown' : 'keypress';
16818     }
16819 };
16820
16821 /**
16822  * Alias for {@link Ext.Loader#onReady Ext.Loader.onReady} with withDomReady set to true
16823  * @member Ext
16824  * @method onReady
16825  */
16826 Ext.onReady = function(fn, scope, options) {
16827     Ext.Loader.onReady(fn, scope, true, options);
16828 };
16829
16830 /**
16831  * Alias for {@link Ext.EventManager#onDocumentReady Ext.EventManager.onDocumentReady}
16832  * @member Ext
16833  * @method onDocumentReady
16834  */
16835 Ext.onDocumentReady = Ext.EventManager.onDocumentReady;
16836
16837 /**
16838  * Alias for {@link Ext.EventManager#addListener Ext.EventManager.addListener}
16839  * @member Ext.EventManager
16840  * @method on
16841  */
16842 Ext.EventManager.on = Ext.EventManager.addListener;
16843
16844 /**
16845  * Alias for {@link Ext.EventManager#removeListener Ext.EventManager.removeListener}
16846  * @member Ext.EventManager
16847  * @method un
16848  */
16849 Ext.EventManager.un = Ext.EventManager.removeListener;
16850
16851 (function(){
16852     var initExtCss = function() {
16853         // find the body element
16854         var bd = document.body || document.getElementsByTagName('body')[0],
16855             baseCSSPrefix = Ext.baseCSSPrefix,
16856             cls = [],
16857             htmlCls = [],
16858             html;
16859
16860         if (!bd) {
16861             return false;
16862         }
16863
16864         html = bd.parentNode;
16865
16866         //Let's keep this human readable!
16867         if (Ext.isIE) {
16868             cls.push(baseCSSPrefix + 'ie');
16869         }
16870         if (Ext.isIE6) {
16871             cls.push(baseCSSPrefix + 'ie6');
16872         }
16873         if (Ext.isIE7) {
16874             cls.push(baseCSSPrefix + 'ie7');
16875         }
16876         if (Ext.isIE8) {
16877             cls.push(baseCSSPrefix + 'ie8');
16878         }
16879         if (Ext.isIE9) {
16880             cls.push(baseCSSPrefix + 'ie9');
16881         }
16882         if (Ext.isGecko) {
16883             cls.push(baseCSSPrefix + 'gecko');
16884         }
16885         if (Ext.isGecko3) {
16886             cls.push(baseCSSPrefix + 'gecko3');
16887         }
16888         if (Ext.isGecko4) {
16889             cls.push(baseCSSPrefix + 'gecko4');
16890         }
16891         if (Ext.isOpera) {
16892             cls.push(baseCSSPrefix + 'opera');
16893         }
16894         if (Ext.isWebKit) {
16895             cls.push(baseCSSPrefix + 'webkit');
16896         }
16897         if (Ext.isSafari) {
16898             cls.push(baseCSSPrefix + 'safari');
16899         }
16900         if (Ext.isSafari2) {
16901             cls.push(baseCSSPrefix + 'safari2');
16902         }
16903         if (Ext.isSafari3) {
16904             cls.push(baseCSSPrefix + 'safari3');
16905         }
16906         if (Ext.isSafari4) {
16907             cls.push(baseCSSPrefix + 'safari4');
16908         }
16909         if (Ext.isChrome) {
16910             cls.push(baseCSSPrefix + 'chrome');
16911         }
16912         if (Ext.isMac) {
16913             cls.push(baseCSSPrefix + 'mac');
16914         }
16915         if (Ext.isLinux) {
16916             cls.push(baseCSSPrefix + 'linux');
16917         }
16918         if (!Ext.supports.CSS3BorderRadius) {
16919             cls.push(baseCSSPrefix + 'nbr');
16920         }
16921         if (!Ext.supports.CSS3LinearGradient) {
16922             cls.push(baseCSSPrefix + 'nlg');
16923         }
16924         if (!Ext.scopeResetCSS) {
16925             cls.push(baseCSSPrefix + 'reset');
16926         }
16927
16928         // add to the parent to allow for selectors x-strict x-border-box, also set the isBorderBox property correctly
16929         if (html) {
16930             if (Ext.isStrict && (Ext.isIE6 || Ext.isIE7)) {
16931                 Ext.isBorderBox = false;
16932             }
16933             else {
16934                 Ext.isBorderBox = true;
16935             }
16936
16937             htmlCls.push(baseCSSPrefix + (Ext.isBorderBox ? 'border-box' : 'strict'));
16938             if (!Ext.isStrict) {
16939                 htmlCls.push(baseCSSPrefix + 'quirks');
16940                 if (Ext.isIE && !Ext.isStrict) {
16941                     Ext.isIEQuirks = true;
16942                 }
16943             }
16944             Ext.fly(html, '_internal').addCls(htmlCls);
16945         }
16946
16947         Ext.fly(bd, '_internal').addCls(cls);
16948         return true;
16949     };
16950
16951     Ext.onReady(initExtCss);
16952 })();
16953
16954 /**
16955  * @class Ext.EventObject
16956
16957 Just as {@link Ext.core.Element} wraps around a native DOM node, Ext.EventObject
16958 wraps the browser's native event-object normalizing cross-browser differences,
16959 such as which mouse button is clicked, keys pressed, mechanisms to stop
16960 event-propagation along with a method to prevent default actions from taking place.
16961
16962 For example:
16963
16964     function handleClick(e, t){ // e is not a standard event object, it is a Ext.EventObject
16965         e.preventDefault();
16966         var target = e.getTarget(); // same as t (the target HTMLElement)
16967         ...
16968     }
16969
16970     var myDiv = {@link Ext#get Ext.get}("myDiv");  // get reference to an {@link Ext.core.Element}
16971     myDiv.on(         // 'on' is shorthand for addListener
16972         "click",      // perform an action on click of myDiv
16973         handleClick   // reference to the action handler
16974     );
16975
16976     // other methods to do the same:
16977     Ext.EventManager.on("myDiv", 'click', handleClick);
16978     Ext.EventManager.addListener("myDiv", 'click', handleClick);
16979
16980  * @singleton
16981  * @markdown
16982  */
16983 Ext.define('Ext.EventObjectImpl', {
16984     uses: ['Ext.util.Point'],
16985
16986     /** Key constant @type Number */
16987     BACKSPACE: 8,
16988     /** Key constant @type Number */
16989     TAB: 9,
16990     /** Key constant @type Number */
16991     NUM_CENTER: 12,
16992     /** Key constant @type Number */
16993     ENTER: 13,
16994     /** Key constant @type Number */
16995     RETURN: 13,
16996     /** Key constant @type Number */
16997     SHIFT: 16,
16998     /** Key constant @type Number */
16999     CTRL: 17,
17000     /** Key constant @type Number */
17001     ALT: 18,
17002     /** Key constant @type Number */
17003     PAUSE: 19,
17004     /** Key constant @type Number */
17005     CAPS_LOCK: 20,
17006     /** Key constant @type Number */
17007     ESC: 27,
17008     /** Key constant @type Number */
17009     SPACE: 32,
17010     /** Key constant @type Number */
17011     PAGE_UP: 33,
17012     /** Key constant @type Number */
17013     PAGE_DOWN: 34,
17014     /** Key constant @type Number */
17015     END: 35,
17016     /** Key constant @type Number */
17017     HOME: 36,
17018     /** Key constant @type Number */
17019     LEFT: 37,
17020     /** Key constant @type Number */
17021     UP: 38,
17022     /** Key constant @type Number */
17023     RIGHT: 39,
17024     /** Key constant @type Number */
17025     DOWN: 40,
17026     /** Key constant @type Number */
17027     PRINT_SCREEN: 44,
17028     /** Key constant @type Number */
17029     INSERT: 45,
17030     /** Key constant @type Number */
17031     DELETE: 46,
17032     /** Key constant @type Number */
17033     ZERO: 48,
17034     /** Key constant @type Number */
17035     ONE: 49,
17036     /** Key constant @type Number */
17037     TWO: 50,
17038     /** Key constant @type Number */
17039     THREE: 51,
17040     /** Key constant @type Number */
17041     FOUR: 52,
17042     /** Key constant @type Number */
17043     FIVE: 53,
17044     /** Key constant @type Number */
17045     SIX: 54,
17046     /** Key constant @type Number */
17047     SEVEN: 55,
17048     /** Key constant @type Number */
17049     EIGHT: 56,
17050     /** Key constant @type Number */
17051     NINE: 57,
17052     /** Key constant @type Number */
17053     A: 65,
17054     /** Key constant @type Number */
17055     B: 66,
17056     /** Key constant @type Number */
17057     C: 67,
17058     /** Key constant @type Number */
17059     D: 68,
17060     /** Key constant @type Number */
17061     E: 69,
17062     /** Key constant @type Number */
17063     F: 70,
17064     /** Key constant @type Number */
17065     G: 71,
17066     /** Key constant @type Number */
17067     H: 72,
17068     /** Key constant @type Number */
17069     I: 73,
17070     /** Key constant @type Number */
17071     J: 74,
17072     /** Key constant @type Number */
17073     K: 75,
17074     /** Key constant @type Number */
17075     L: 76,
17076     /** Key constant @type Number */
17077     M: 77,
17078     /** Key constant @type Number */
17079     N: 78,
17080     /** Key constant @type Number */
17081     O: 79,
17082     /** Key constant @type Number */
17083     P: 80,
17084     /** Key constant @type Number */
17085     Q: 81,
17086     /** Key constant @type Number */
17087     R: 82,
17088     /** Key constant @type Number */
17089     S: 83,
17090     /** Key constant @type Number */
17091     T: 84,
17092     /** Key constant @type Number */
17093     U: 85,
17094     /** Key constant @type Number */
17095     V: 86,
17096     /** Key constant @type Number */
17097     W: 87,
17098     /** Key constant @type Number */
17099     X: 88,
17100     /** Key constant @type Number */
17101     Y: 89,
17102     /** Key constant @type Number */
17103     Z: 90,
17104     /** Key constant @type Number */
17105     CONTEXT_MENU: 93,
17106     /** Key constant @type Number */
17107     NUM_ZERO: 96,
17108     /** Key constant @type Number */
17109     NUM_ONE: 97,
17110     /** Key constant @type Number */
17111     NUM_TWO: 98,
17112     /** Key constant @type Number */
17113     NUM_THREE: 99,
17114     /** Key constant @type Number */
17115     NUM_FOUR: 100,
17116     /** Key constant @type Number */
17117     NUM_FIVE: 101,
17118     /** Key constant @type Number */
17119     NUM_SIX: 102,
17120     /** Key constant @type Number */
17121     NUM_SEVEN: 103,
17122     /** Key constant @type Number */
17123     NUM_EIGHT: 104,
17124     /** Key constant @type Number */
17125     NUM_NINE: 105,
17126     /** Key constant @type Number */
17127     NUM_MULTIPLY: 106,
17128     /** Key constant @type Number */
17129     NUM_PLUS: 107,
17130     /** Key constant @type Number */
17131     NUM_MINUS: 109,
17132     /** Key constant @type Number */
17133     NUM_PERIOD: 110,
17134     /** Key constant @type Number */
17135     NUM_DIVISION: 111,
17136     /** Key constant @type Number */
17137     F1: 112,
17138     /** Key constant @type Number */
17139     F2: 113,
17140     /** Key constant @type Number */
17141     F3: 114,
17142     /** Key constant @type Number */
17143     F4: 115,
17144     /** Key constant @type Number */
17145     F5: 116,
17146     /** Key constant @type Number */
17147     F6: 117,
17148     /** Key constant @type Number */
17149     F7: 118,
17150     /** Key constant @type Number */
17151     F8: 119,
17152     /** Key constant @type Number */
17153     F9: 120,
17154     /** Key constant @type Number */
17155     F10: 121,
17156     /** Key constant @type Number */
17157     F11: 122,
17158     /** Key constant @type Number */
17159     F12: 123,
17160
17161     /**
17162      * Simple click regex
17163      * @private
17164      */
17165     clickRe: /(dbl)?click/,
17166     // safari keypress events for special keys return bad keycodes
17167     safariKeys: {
17168         3: 13, // enter
17169         63234: 37, // left
17170         63235: 39, // right
17171         63232: 38, // up
17172         63233: 40, // down
17173         63276: 33, // page up
17174         63277: 34, // page down
17175         63272: 46, // delete
17176         63273: 36, // home
17177         63275: 35 // end
17178     },
17179     // normalize button clicks, don't see any way to feature detect this.
17180     btnMap: Ext.isIE ? {
17181         1: 0,
17182         4: 1,
17183         2: 2
17184     } : {
17185         0: 0,
17186         1: 1,
17187         2: 2
17188     },
17189
17190     constructor: function(event, freezeEvent){
17191         if (event) {
17192             this.setEvent(event.browserEvent || event, freezeEvent);
17193         }
17194     },
17195
17196     setEvent: function(event, freezeEvent){
17197         var me = this, button, options;
17198
17199         if (event == me || (event && event.browserEvent)) { // already wrapped
17200             return event;
17201         }
17202         me.browserEvent = event;
17203         if (event) {
17204             // normalize buttons
17205             button = event.button ? me.btnMap[event.button] : (event.which ? event.which - 1 : -1);
17206             if (me.clickRe.test(event.type) && button == -1) {
17207                 button = 0;
17208             }
17209             options = {
17210                 type: event.type,
17211                 button: button,
17212                 shiftKey: event.shiftKey,
17213                 // mac metaKey behaves like ctrlKey
17214                 ctrlKey: event.ctrlKey || event.metaKey || false,
17215                 altKey: event.altKey,
17216                 // in getKey these will be normalized for the mac
17217                 keyCode: event.keyCode,
17218                 charCode: event.charCode,
17219                 // cache the targets for the delayed and or buffered events
17220                 target: Ext.EventManager.getTarget(event),
17221                 relatedTarget: Ext.EventManager.getRelatedTarget(event),
17222                 currentTarget: event.currentTarget,
17223                 xy: (freezeEvent ? me.getXY() : null)
17224             };
17225         } else {
17226             options = {
17227                 button: -1,
17228                 shiftKey: false,
17229                 ctrlKey: false,
17230                 altKey: false,
17231                 keyCode: 0,
17232                 charCode: 0,
17233                 target: null,
17234                 xy: [0, 0]
17235             };
17236         }
17237         Ext.apply(me, options);
17238         return me;
17239     },
17240
17241     /**
17242      * Stop the event (preventDefault and stopPropagation)
17243      */
17244     stopEvent: function(){
17245         this.stopPropagation();
17246         this.preventDefault();
17247     },
17248
17249     /**
17250      * Prevents the browsers default handling of the event.
17251      */
17252     preventDefault: function(){
17253         if (this.browserEvent) {
17254             Ext.EventManager.preventDefault(this.browserEvent);
17255         }
17256     },
17257
17258     /**
17259      * Cancels bubbling of the event.
17260      */
17261     stopPropagation: function(){
17262         var browserEvent = this.browserEvent;
17263
17264         if (browserEvent) {
17265             if (browserEvent.type == 'mousedown') {
17266                 Ext.EventManager.stoppedMouseDownEvent.fire(this);
17267             }
17268             Ext.EventManager.stopPropagation(browserEvent);
17269         }
17270     },
17271
17272     /**
17273      * Gets the character code for the event.
17274      * @return {Number}
17275      */
17276     getCharCode: function(){
17277         return this.charCode || this.keyCode;
17278     },
17279
17280     /**
17281      * Returns a normalized keyCode for the event.
17282      * @return {Number} The key code
17283      */
17284     getKey: function(){
17285         return this.normalizeKey(this.keyCode || this.charCode);
17286     },
17287
17288     /**
17289      * Normalize key codes across browsers
17290      * @private
17291      * @param {Number} key The key code
17292      * @return {Number} The normalized code
17293      */
17294     normalizeKey: function(key){
17295         // can't feature detect this
17296         return Ext.isWebKit ? (this.safariKeys[key] || key) : key;
17297     },
17298
17299     /**
17300      * Gets the x coordinate of the event.
17301      * @return {Number}
17302      * @deprecated 4.0 Replaced by {@link #getX}
17303      */
17304     getPageX: function(){
17305         return this.getX();
17306     },
17307
17308     /**
17309      * Gets the y coordinate of the event.
17310      * @return {Number}
17311      * @deprecated 4.0 Replaced by {@link #getY}
17312      */
17313     getPageY: function(){
17314         return this.getY();
17315     },
17316     
17317     /**
17318      * Gets the x coordinate of the event.
17319      * @return {Number}
17320      */
17321     getX: function() {
17322         return this.getXY()[0];
17323     },    
17324     
17325     /**
17326      * Gets the y coordinate of the event.
17327      * @return {Number}
17328      */
17329     getY: function() {
17330         return this.getXY()[1];
17331     },
17332         
17333     /**
17334      * Gets the page coordinates of the event.
17335      * @return {Array} The xy values like [x, y]
17336      */
17337     getXY: function() {
17338         if (!this.xy) {
17339             // same for XY
17340             this.xy = Ext.EventManager.getPageXY(this.browserEvent);
17341         }
17342         return this.xy;
17343     },
17344
17345     /**
17346      * Gets the target for the event.
17347      * @param {String} selector (optional) A simple selector to filter the target or look for an ancestor of the target
17348      * @param {Number/Mixed} maxDepth (optional) The max depth to search as a number or element (defaults to 10 || document.body)
17349      * @param {Boolean} returnEl (optional) True to return a Ext.core.Element object instead of DOM node
17350      * @return {HTMLelement}
17351      */
17352     getTarget : function(selector, maxDepth, returnEl){
17353         if (selector) {
17354             return Ext.fly(this.target).findParent(selector, maxDepth, returnEl);
17355         }
17356         return returnEl ? Ext.get(this.target) : this.target;
17357     },
17358
17359     /**
17360      * Gets the related target.
17361      * @param {String} selector (optional) A simple selector to filter the target or look for an ancestor of the target
17362      * @param {Number/Mixed} maxDepth (optional) The max depth to search as a number or element (defaults to 10 || document.body)
17363      * @param {Boolean} returnEl (optional) True to return a Ext.core.Element object instead of DOM node
17364      * @return {HTMLElement}
17365      */
17366     getRelatedTarget : function(selector, maxDepth, returnEl){
17367         if (selector) {
17368             return Ext.fly(this.relatedTarget).findParent(selector, maxDepth, returnEl);
17369         }
17370         return returnEl ? Ext.get(this.relatedTarget) : this.relatedTarget;
17371     },
17372
17373     /**
17374      * Normalizes mouse wheel delta across browsers
17375      * @return {Number} The delta
17376      */
17377     getWheelDelta : function(){
17378         var event = this.browserEvent,
17379             delta = 0;
17380
17381         if (event.wheelDelta) { /* IE/Opera. */
17382             delta = event.wheelDelta / 120;
17383         } else if (event.detail){ /* Mozilla case. */
17384             delta = -event.detail / 3;
17385         }
17386         return delta;
17387     },
17388
17389     /**
17390     * 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.
17391     * Example usage:<pre><code>
17392 // Handle click on any child of an element
17393 Ext.getBody().on('click', function(e){
17394     if(e.within('some-el')){
17395         alert('Clicked on a child of some-el!');
17396     }
17397 });
17398
17399 // Handle click directly on an element, ignoring clicks on child nodes
17400 Ext.getBody().on('click', function(e,t){
17401     if((t.id == 'some-el') && !e.within(t, true)){
17402         alert('Clicked directly on some-el!');
17403     }
17404 });
17405 </code></pre>
17406      * @param {Mixed} el The id, DOM element or Ext.core.Element to check
17407      * @param {Boolean} related (optional) true to test if the related target is within el instead of the target
17408      * @param {Boolean} allowEl {optional} true to also check if the passed element is the target or related target
17409      * @return {Boolean}
17410      */
17411     within : function(el, related, allowEl){
17412         if(el){
17413             var t = related ? this.getRelatedTarget() : this.getTarget(),
17414                 result;
17415
17416             if (t) {
17417                 result = Ext.fly(el).contains(t);
17418                 if (!result && allowEl) {
17419                     result = t == Ext.getDom(el);
17420                 }
17421                 return result;
17422             }
17423         }
17424         return false;
17425     },
17426
17427     /**
17428      * Checks if the key pressed was a "navigation" key
17429      * @return {Boolean} True if the press is a navigation keypress
17430      */
17431     isNavKeyPress : function(){
17432         var me = this,
17433             k = this.normalizeKey(me.keyCode);
17434
17435        return (k >= 33 && k <= 40) ||  // Page Up/Down, End, Home, Left, Up, Right, Down
17436        k == me.RETURN ||
17437        k == me.TAB ||
17438        k == me.ESC;
17439     },
17440
17441     /**
17442      * Checks if the key pressed was a "special" key
17443      * @return {Boolean} True if the press is a special keypress
17444      */
17445     isSpecialKey : function(){
17446         var k = this.normalizeKey(this.keyCode);
17447         return (this.type == 'keypress' && this.ctrlKey) ||
17448         this.isNavKeyPress() ||
17449         (k == this.BACKSPACE) || // Backspace
17450         (k >= 16 && k <= 20) || // Shift, Ctrl, Alt, Pause, Caps Lock
17451         (k >= 44 && k <= 46);   // Print Screen, Insert, Delete
17452     },
17453
17454     /**
17455      * Returns a point object that consists of the object coordinates.
17456      * @return {Ext.util.Point} point
17457      */
17458     getPoint : function(){
17459         var xy = this.getXY();
17460         return Ext.create('Ext.util.Point', xy[0], xy[1]);
17461     },
17462
17463    /**
17464     * Returns true if the control, meta, shift or alt key was pressed during this event.
17465     * @return {Boolean}
17466     */
17467     hasModifier : function(){
17468         return this.ctrlKey || this.altKey || this.shiftKey || this.metaKey;
17469     },
17470
17471     /**
17472      * Injects a DOM event using the data in this object and (optionally) a new target.
17473      * This is a low-level technique and not likely to be used by application code. The
17474      * currently supported event types are:
17475      * <p><b>HTMLEvents</b></p>
17476      * <ul>
17477      * <li>load</li>
17478      * <li>unload</li>
17479      * <li>select</li>
17480      * <li>change</li>
17481      * <li>submit</li>
17482      * <li>reset</li>
17483      * <li>resize</li>
17484      * <li>scroll</li>
17485      * </ul>
17486      * <p><b>MouseEvents</b></p>
17487      * <ul>
17488      * <li>click</li>
17489      * <li>dblclick</li>
17490      * <li>mousedown</li>
17491      * <li>mouseup</li>
17492      * <li>mouseover</li>
17493      * <li>mousemove</li>
17494      * <li>mouseout</li>
17495      * </ul>
17496      * <p><b>UIEvents</b></p>
17497      * <ul>
17498      * <li>focusin</li>
17499      * <li>focusout</li>
17500      * <li>activate</li>
17501      * <li>focus</li>
17502      * <li>blur</li>
17503      * </ul>
17504      * @param {Element/HTMLElement} target If specified, the target for the event. This
17505      * is likely to be used when relaying a DOM event. If not specified, {@link #getTarget}
17506      * is used to determine the target.
17507      */
17508     injectEvent: function () {
17509         var API,
17510             dispatchers = {}; // keyed by event type (e.g., 'mousedown')
17511
17512         // Good reference: http://developer.yahoo.com/yui/docs/UserAction.js.html
17513
17514         // IE9 has createEvent, but this code causes major problems with htmleditor (it
17515         // blocks all mouse events and maybe more). TODO
17516
17517         if (!Ext.isIE && document.createEvent) { // if (DOM compliant)
17518             API = {
17519                 createHtmlEvent: function (doc, type, bubbles, cancelable) {
17520                     var event = doc.createEvent('HTMLEvents');
17521
17522                     event.initEvent(type, bubbles, cancelable);
17523                     return event;
17524                 },
17525
17526                 createMouseEvent: function (doc, type, bubbles, cancelable, detail,
17527                                             clientX, clientY, ctrlKey, altKey, shiftKey, metaKey,
17528                                             button, relatedTarget) {
17529                     var event = doc.createEvent('MouseEvents'),
17530                         view = doc.defaultView || window;
17531
17532                     if (event.initMouseEvent) {
17533                         event.initMouseEvent(type, bubbles, cancelable, view, detail,
17534                                     clientX, clientY, clientX, clientY, ctrlKey, altKey,
17535                                     shiftKey, metaKey, button, relatedTarget);
17536                     } else { // old Safari
17537                         event = doc.createEvent('UIEvents');
17538                         event.initEvent(type, bubbles, cancelable);
17539                         event.view = view;
17540                         event.detail = detail;
17541                         event.screenX = clientX;
17542                         event.screenY = clientY;
17543                         event.clientX = clientX;
17544                         event.clientY = clientY;
17545                         event.ctrlKey = ctrlKey;
17546                         event.altKey = altKey;
17547                         event.metaKey = metaKey;
17548                         event.shiftKey = shiftKey;
17549                         event.button = button;
17550                         event.relatedTarget = relatedTarget;
17551                     }
17552
17553                     return event;
17554                 },
17555
17556                 createUIEvent: function (doc, type, bubbles, cancelable, detail) {
17557                     var event = doc.createEvent('UIEvents'),
17558                         view = doc.defaultView || window;
17559
17560                     event.initUIEvent(type, bubbles, cancelable, view, detail);
17561                     return event;
17562                 },
17563
17564                 fireEvent: function (target, type, event) {
17565                     target.dispatchEvent(event);
17566                 },
17567
17568                 fixTarget: function (target) {
17569                     // Safari3 doesn't have window.dispatchEvent()
17570                     if (target == window && !target.dispatchEvent) {
17571                         return document;
17572                     }
17573
17574                     return target;
17575                 }
17576             }
17577         } else if (document.createEventObject) { // else if (IE)
17578             var crazyIEButtons = { 0: 1, 1: 4, 2: 2 };
17579
17580             API = {
17581                 createHtmlEvent: function (doc, type, bubbles, cancelable) {
17582                     var event = doc.createEventObject();
17583                     event.bubbles = bubbles;
17584                     event.cancelable = cancelable;
17585                     return event;
17586                 },
17587
17588                 createMouseEvent: function (doc, type, bubbles, cancelable, detail,
17589                                             clientX, clientY, ctrlKey, altKey, shiftKey, metaKey,
17590                                             button, relatedTarget) {
17591                     var event = doc.createEventObject();
17592                     event.bubbles = bubbles;
17593                     event.cancelable = cancelable;
17594                     event.detail = detail;
17595                     event.screenX = clientX;
17596                     event.screenY = clientY;
17597                     event.clientX = clientX;
17598                     event.clientY = clientY;
17599                     event.ctrlKey = ctrlKey;
17600                     event.altKey = altKey;
17601                     event.shiftKey = shiftKey;
17602                     event.metaKey = metaKey;
17603                     event.button = crazyIEButtons[button] || button;
17604                     event.relatedTarget = relatedTarget; // cannot assign to/fromElement
17605                     return event;
17606                 },
17607
17608                 createUIEvent: function (doc, type, bubbles, cancelable, detail) {
17609                     var event = doc.createEventObject();
17610                     event.bubbles = bubbles;
17611                     event.cancelable = cancelable;
17612                     return event;
17613                 },
17614
17615                 fireEvent: function (target, type, event) {
17616                     target.fireEvent('on' + type, event);
17617                 },
17618
17619                 fixTarget: function (target) {
17620                     if (target == document) {
17621                         // IE6,IE7 thinks window==document and doesn't have window.fireEvent()
17622                         // IE6,IE7 cannot properly call document.fireEvent()
17623                         return document.documentElement;
17624                     }
17625
17626                     return target;
17627                 }
17628             };
17629         }
17630
17631         //----------------
17632         // HTMLEvents
17633
17634         Ext.Object.each({
17635                 load:   [false, false],
17636                 unload: [false, false],
17637                 select: [true, false],
17638                 change: [true, false],
17639                 submit: [true, true],
17640                 reset:  [true, false],
17641                 resize: [true, false],
17642                 scroll: [true, false]
17643             },
17644             function (name, value) {
17645                 var bubbles = value[0], cancelable = value[1];
17646                 dispatchers[name] = function (targetEl, srcEvent) {
17647                     var e = API.createHtmlEvent(name, bubbles, cancelable);
17648                     API.fireEvent(targetEl, name, e);
17649                 };
17650             });
17651
17652         //----------------
17653         // MouseEvents
17654
17655         function createMouseEventDispatcher (type, detail) {
17656             var cancelable = (type != 'mousemove');
17657             return function (targetEl, srcEvent) {
17658                 var xy = srcEvent.getXY(),
17659                     e = API.createMouseEvent(targetEl.ownerDocument, type, true, cancelable,
17660                                 detail, xy[0], xy[1], srcEvent.ctrlKey, srcEvent.altKey,
17661                                 srcEvent.shiftKey, srcEvent.metaKey, srcEvent.button,
17662                                 srcEvent.relatedTarget);
17663                 API.fireEvent(targetEl, type, e);
17664             };
17665         }
17666
17667         Ext.each(['click', 'dblclick', 'mousedown', 'mouseup', 'mouseover', 'mousemove', 'mouseout'],
17668             function (eventName) {
17669                 dispatchers[eventName] = createMouseEventDispatcher(eventName, 1);
17670             });
17671
17672         //----------------
17673         // UIEvents
17674
17675         Ext.Object.each({
17676                 focusin:  [true, false],
17677                 focusout: [true, false],
17678                 activate: [true, true],
17679                 focus:    [false, false],
17680                 blur:     [false, false]
17681             },
17682             function (name, value) {
17683                 var bubbles = value[0], cancelable = value[1];
17684                 dispatchers[name] = function (targetEl, srcEvent) {
17685                     var e = API.createUIEvent(targetEl.ownerDocument, name, bubbles, cancelable, 1);
17686                     API.fireEvent(targetEl, name, e);
17687                 };
17688             });
17689
17690         //---------
17691         if (!API) {
17692             // not even sure what ancient browsers fall into this category...
17693
17694             dispatchers = {}; // never mind all those we just built :P
17695
17696             API = {
17697                 fixTarget: function (t) {
17698                     return t;
17699                 }
17700             };
17701         }
17702
17703         function cannotInject (target, srcEvent) {
17704             // TODO log something
17705         }
17706
17707         return function (target) {
17708             var me = this,
17709                 dispatcher = dispatchers[me.type] || cannotInject,
17710                 t = target ? (target.dom || target) : me.getTarget();
17711
17712             t = API.fixTarget(t);
17713             dispatcher(t, me);
17714         };
17715     }() // call to produce method
17716
17717 }, function() {
17718
17719 Ext.EventObject = new Ext.EventObjectImpl();
17720
17721 });
17722
17723
17724 /**
17725  * @class Ext.core.Element
17726  */
17727 (function(){
17728     var doc = document,
17729         isCSS1 = doc.compatMode == "CSS1Compat",
17730         ELEMENT = Ext.core.Element,
17731         fly = function(el){
17732             if (!_fly) {
17733                 _fly = new Ext.core.Element.Flyweight();
17734             }
17735             _fly.dom = el;
17736             return _fly;
17737         }, _fly;
17738
17739     Ext.apply(ELEMENT, {
17740         isAncestor : function(p, c) {
17741             var ret = false;
17742
17743             p = Ext.getDom(p);
17744             c = Ext.getDom(c);
17745             if (p && c) {
17746                 if (p.contains) {
17747                     return p.contains(c);
17748                 } else if (p.compareDocumentPosition) {
17749                     return !!(p.compareDocumentPosition(c) & 16);
17750                 } else {
17751                     while ((c = c.parentNode)) {
17752                         ret = c == p || ret;
17753                     }
17754                 }
17755             }
17756             return ret;
17757         },
17758
17759         getViewWidth : function(full) {
17760             return full ? ELEMENT.getDocumentWidth() : ELEMENT.getViewportWidth();
17761         },
17762
17763         getViewHeight : function(full) {
17764             return full ? ELEMENT.getDocumentHeight() : ELEMENT.getViewportHeight();
17765         },
17766
17767         getDocumentHeight: function() {
17768             return Math.max(!isCSS1 ? doc.body.scrollHeight : doc.documentElement.scrollHeight, ELEMENT.getViewportHeight());
17769         },
17770
17771         getDocumentWidth: function() {
17772             return Math.max(!isCSS1 ? doc.body.scrollWidth : doc.documentElement.scrollWidth, ELEMENT.getViewportWidth());
17773         },
17774
17775         getViewportHeight: function(){
17776             return Ext.isIE ?
17777                    (Ext.isStrict ? doc.documentElement.clientHeight : doc.body.clientHeight) :
17778                    self.innerHeight;
17779         },
17780
17781         getViewportWidth : function() {
17782             return (!Ext.isStrict && !Ext.isOpera) ? doc.body.clientWidth :
17783                    Ext.isIE ? doc.documentElement.clientWidth : self.innerWidth;
17784         },
17785
17786         getY : function(el) {
17787             return ELEMENT.getXY(el)[1];
17788         },
17789
17790         getX : function(el) {
17791             return ELEMENT.getXY(el)[0];
17792         },
17793
17794         getXY : function(el) {
17795             var p,
17796                 pe,
17797                 b,
17798                 bt,
17799                 bl,
17800                 dbd,
17801                 x = 0,
17802                 y = 0,
17803                 scroll,
17804                 hasAbsolute,
17805                 bd = (doc.body || doc.documentElement),
17806                 ret = [0,0];
17807
17808             el = Ext.getDom(el);
17809
17810             if(el != bd){
17811                 hasAbsolute = fly(el).isStyle("position", "absolute");
17812
17813                 if (el.getBoundingClientRect) {
17814                     b = el.getBoundingClientRect();
17815                     scroll = fly(document).getScroll();
17816                     ret = [Math.round(b.left + scroll.left), Math.round(b.top + scroll.top)];
17817                 } else {
17818                     p = el;
17819
17820                     while (p) {
17821                         pe = fly(p);
17822                         x += p.offsetLeft;
17823                         y += p.offsetTop;
17824
17825                         hasAbsolute = hasAbsolute || pe.isStyle("position", "absolute");
17826
17827                         if (Ext.isGecko) {
17828                             y += bt = parseInt(pe.getStyle("borderTopWidth"), 10) || 0;
17829                             x += bl = parseInt(pe.getStyle("borderLeftWidth"), 10) || 0;
17830
17831                             if (p != el && !pe.isStyle('overflow','visible')) {
17832                                 x += bl;
17833                                 y += bt;
17834                             }
17835                         }
17836                         p = p.offsetParent;
17837                     }
17838
17839                     if (Ext.isSafari && hasAbsolute) {
17840                         x -= bd.offsetLeft;
17841                         y -= bd.offsetTop;
17842                     }
17843
17844                     if (Ext.isGecko && !hasAbsolute) {
17845                         dbd = fly(bd);
17846                         x += parseInt(dbd.getStyle("borderLeftWidth"), 10) || 0;
17847                         y += parseInt(dbd.getStyle("borderTopWidth"), 10) || 0;
17848                     }
17849
17850                     p = el.parentNode;
17851                     while (p && p != bd) {
17852                         if (!Ext.isOpera || (p.tagName != 'TR' && !fly(p).isStyle("display", "inline"))) {
17853                             x -= p.scrollLeft;
17854                             y -= p.scrollTop;
17855                         }
17856                         p = p.parentNode;
17857                     }
17858                     ret = [x,y];
17859                 }
17860             }
17861             return ret;
17862         },
17863
17864         setXY : function(el, xy) {
17865             (el = Ext.fly(el, '_setXY')).position();
17866
17867             var pts = el.translatePoints(xy),
17868                 style = el.dom.style,
17869                 pos;
17870
17871             for (pos in pts) {
17872                 if (!isNaN(pts[pos])) {
17873                     style[pos] = pts[pos] + "px";
17874                 }
17875             }
17876         },
17877
17878         setX : function(el, x) {
17879             ELEMENT.setXY(el, [x, false]);
17880         },
17881
17882         setY : function(el, y) {
17883             ELEMENT.setXY(el, [false, y]);
17884         },
17885
17886         /**
17887          * Serializes a DOM form into a url encoded string
17888          * @param {Object} form The form
17889          * @return {String} The url encoded form
17890          */
17891         serializeForm: function(form) {
17892             var fElements = form.elements || (document.forms[form] || Ext.getDom(form)).elements,
17893                 hasSubmit = false,
17894                 encoder = encodeURIComponent,
17895                 name,
17896                 data = '',
17897                 type,
17898                 hasValue;
17899
17900             Ext.each(fElements, function(element){
17901                 name = element.name;
17902                 type = element.type;
17903
17904                 if (!element.disabled && name) {
17905                     if (/select-(one|multiple)/i.test(type)) {
17906                         Ext.each(element.options, function(opt){
17907                             if (opt.selected) {
17908                                 hasValue = opt.hasAttribute ? opt.hasAttribute('value') : opt.getAttributeNode('value').specified;
17909                                 data += String.format("{0}={1}&", encoder(name), encoder(hasValue ? opt.value : opt.text));
17910                             }
17911                         });
17912                     } else if (!(/file|undefined|reset|button/i.test(type))) {
17913                         if (!(/radio|checkbox/i.test(type) && !element.checked) && !(type == 'submit' && hasSubmit)) {
17914                             data += encoder(name) + '=' + encoder(element.value) + '&';
17915                             hasSubmit = /submit/i.test(type);
17916                         }
17917                     }
17918                 }
17919             });
17920             return data.substr(0, data.length - 1);
17921         }
17922     });
17923 })();
17924
17925 /**
17926  * @class Ext.core.Element
17927  */
17928
17929 Ext.core.Element.addMethods({
17930
17931     /**
17932      * Monitors this Element for the mouse leaving. Calls the function after the specified delay only if
17933      * the mouse was not moved back into the Element within the delay. If the mouse <i>was</i> moved
17934      * back in, the function is not called.
17935      * @param {Number} delay The delay <b>in milliseconds</b> to wait for possible mouse re-entry before calling the handler function.
17936      * @param {Function} handler The function to call if the mouse remains outside of this Element for the specified time.
17937      * @param {Object} scope The scope (<code>this</code> reference) in which the handler function executes. Defaults to this Element.
17938      * @return {Object} The listeners object which was added to this element so that monitoring can be stopped. Example usage:</pre><code>
17939 // Hide the menu if the mouse moves out for 250ms or more
17940 this.mouseLeaveMonitor = this.menuEl.monitorMouseLeave(250, this.hideMenu, this);
17941
17942 ...
17943 // Remove mouseleave monitor on menu destroy
17944 this.menuEl.un(this.mouseLeaveMonitor);
17945 </code></pre>
17946      */
17947     monitorMouseLeave: function(delay, handler, scope) {
17948         var me = this,
17949             timer,
17950             listeners = {
17951                 mouseleave: function(e) {
17952                     timer = setTimeout(Ext.Function.bind(handler, scope||me, [e]), delay);
17953                 },
17954                 mouseenter: function() {
17955                     clearTimeout(timer);
17956                 },
17957                 freezeEvent: true
17958             };
17959
17960         me.on(listeners);
17961         return listeners;
17962     },
17963
17964     /**
17965      * Stops the specified event(s) from bubbling and optionally prevents the default action
17966      * @param {String/Array} eventName an event / array of events to stop from bubbling
17967      * @param {Boolean} preventDefault (optional) true to prevent the default action too
17968      * @return {Ext.core.Element} this
17969      */
17970     swallowEvent : function(eventName, preventDefault) {
17971         var me = this;
17972         function fn(e) {
17973             e.stopPropagation();
17974             if (preventDefault) {
17975                 e.preventDefault();
17976             }
17977         }
17978         
17979         if (Ext.isArray(eventName)) {
17980             Ext.each(eventName, function(e) {
17981                  me.on(e, fn);
17982             });
17983             return me;
17984         }
17985         me.on(eventName, fn);
17986         return me;
17987     },
17988
17989     /**
17990      * Create an event handler on this element such that when the event fires and is handled by this element,
17991      * it will be relayed to another object (i.e., fired again as if it originated from that object instead).
17992      * @param {String} eventName The type of event to relay
17993      * @param {Object} object Any object that extends {@link Ext.util.Observable} that will provide the context
17994      * for firing the relayed event
17995      */
17996     relayEvent : function(eventName, observable) {
17997         this.on(eventName, function(e) {
17998             observable.fireEvent(eventName, e);
17999         });
18000     },
18001
18002     /**
18003      * Removes Empty, or whitespace filled text nodes. Combines adjacent text nodes.
18004      * @param {Boolean} forceReclean (optional) By default the element
18005      * keeps track if it has been cleaned already so
18006      * you can call this over and over. However, if you update the element and
18007      * need to force a reclean, you can pass true.
18008      */
18009     clean : function(forceReclean) {
18010         var me  = this,
18011             dom = me.dom,
18012             n   = dom.firstChild,
18013             nx,
18014             ni  = -1;
18015
18016         if (Ext.core.Element.data(dom, 'isCleaned') && forceReclean !== true) {
18017             return me;
18018         }
18019
18020         while (n) {
18021             nx = n.nextSibling;
18022             if (n.nodeType == 3) {
18023                 // Remove empty/whitespace text nodes
18024                 if (!(/\S/.test(n.nodeValue))) {
18025                     dom.removeChild(n);
18026                 // Combine adjacent text nodes
18027                 } else if (nx && nx.nodeType == 3) {
18028                     n.appendData(Ext.String.trim(nx.data));
18029                     dom.removeChild(nx);
18030                     nx = n.nextSibling;
18031                     n.nodeIndex = ++ni;
18032                 }
18033             } else {
18034                 // Recursively clean
18035                 Ext.fly(n).clean();
18036                 n.nodeIndex = ++ni;
18037             }
18038             n = nx;
18039         }
18040
18041         Ext.core.Element.data(dom, 'isCleaned', true);
18042         return me;
18043     },
18044
18045     /**
18046      * Direct access to the Ext.ElementLoader {@link Ext.ElementLoader#load} method. The method takes the same object
18047      * parameter as {@link Ext.ElementLoader#load}
18048      * @return {Ext.core.Element} this
18049      */
18050     load : function(options) {
18051         this.getLoader().load(options);
18052         return this;
18053     },
18054
18055     /**
18056     * Gets this element's {@link Ext.ElementLoader ElementLoader}
18057     * @return {Ext.ElementLoader} The loader
18058     */
18059     getLoader : function() {
18060         var dom = this.dom,
18061             data = Ext.core.Element.data,
18062             loader = data(dom, 'loader');
18063             
18064         if (!loader) {
18065             loader = Ext.create('Ext.ElementLoader', {
18066                 target: this
18067             });
18068             data(dom, 'loader', loader);
18069         }
18070         return loader;
18071     },
18072
18073     /**
18074     * Update the innerHTML of this element, optionally searching for and processing scripts
18075     * @param {String} html The new HTML
18076     * @param {Boolean} loadScripts (optional) True to look for and process scripts (defaults to false)
18077     * @param {Function} callback (optional) For async script loading you can be notified when the update completes
18078     * @return {Ext.core.Element} this
18079      */
18080     update : function(html, loadScripts, callback) {
18081         var me = this,
18082             id,
18083             dom,
18084             interval;
18085             
18086         if (!me.dom) {
18087             return me;
18088         }
18089         html = html || '';
18090         dom = me.dom;
18091
18092         if (loadScripts !== true) {
18093             dom.innerHTML = html;
18094             Ext.callback(callback, me);
18095             return me;
18096         }
18097
18098         id  = Ext.id();
18099         html += '<span id="' + id + '"></span>';
18100
18101         interval = setInterval(function(){
18102             if (!document.getElementById(id)) {
18103                 return false;    
18104             }
18105             clearInterval(interval);
18106             var DOC    = document,
18107                 hd     = DOC.getElementsByTagName("head")[0],
18108                 re     = /(?:<script([^>]*)?>)((\n|\r|.)*?)(?:<\/script>)/ig,
18109                 srcRe  = /\ssrc=([\'\"])(.*?)\1/i,
18110                 typeRe = /\stype=([\'\"])(.*?)\1/i,
18111                 match,
18112                 attrs,
18113                 srcMatch,
18114                 typeMatch,
18115                 el,
18116                 s;
18117
18118             while ((match = re.exec(html))) {
18119                 attrs = match[1];
18120                 srcMatch = attrs ? attrs.match(srcRe) : false;
18121                 if (srcMatch && srcMatch[2]) {
18122                    s = DOC.createElement("script");
18123                    s.src = srcMatch[2];
18124                    typeMatch = attrs.match(typeRe);
18125                    if (typeMatch && typeMatch[2]) {
18126                        s.type = typeMatch[2];
18127                    }
18128                    hd.appendChild(s);
18129                 } else if (match[2] && match[2].length > 0) {
18130                     if (window.execScript) {
18131                        window.execScript(match[2]);
18132                     } else {
18133                        window.eval(match[2]);
18134                     }
18135                 }
18136             }
18137             
18138             el = DOC.getElementById(id);
18139             if (el) {
18140                 Ext.removeNode(el);
18141             }
18142             Ext.callback(callback, me);
18143         }, 20);
18144         dom.innerHTML = html.replace(/(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)/ig, '');
18145         return me;
18146     },
18147
18148     // inherit docs, overridden so we can add removeAnchor
18149     removeAllListeners : function() {
18150         this.removeAnchor();
18151         Ext.EventManager.removeAll(this.dom);
18152         return this;
18153     },
18154
18155     /**
18156      * Creates a proxy element of this element
18157      * @param {String/Object} config The class name of the proxy element or a DomHelper config object
18158      * @param {String/HTMLElement} renderTo (optional) The element or element id to render the proxy to (defaults to document.body)
18159      * @param {Boolean} matchBox (optional) True to align and size the proxy to this element now (defaults to false)
18160      * @return {Ext.core.Element} The new proxy element
18161      */
18162     createProxy : function(config, renderTo, matchBox) {
18163         config = (typeof config == 'object') ? config : {tag : "div", cls: config};
18164
18165         var me = this,
18166             proxy = renderTo ? Ext.core.DomHelper.append(renderTo, config, true) :
18167                                Ext.core.DomHelper.insertBefore(me.dom, config, true);
18168
18169         proxy.setVisibilityMode(Ext.core.Element.DISPLAY);
18170         proxy.hide();
18171         if (matchBox && me.setBox && me.getBox) { // check to make sure Element.position.js is loaded
18172            proxy.setBox(me.getBox());
18173         }
18174         return proxy;
18175     }
18176 });
18177 Ext.core.Element.prototype.clearListeners = Ext.core.Element.prototype.removeAllListeners;
18178
18179 /**
18180  * @class Ext.core.Element
18181  */
18182 Ext.core.Element.addMethods({
18183     /**
18184      * Gets the x,y coordinates specified by the anchor position on the element.
18185      * @param {String} anchor (optional) The specified anchor position (defaults to "c").  See {@link #alignTo}
18186      * for details on supported anchor positions.
18187      * @param {Boolean} local (optional) True to get the local (element top/left-relative) anchor position instead
18188      * of page coordinates
18189      * @param {Object} size (optional) An object containing the size to use for calculating anchor position
18190      * {width: (target width), height: (target height)} (defaults to the element's current size)
18191      * @return {Array} [x, y] An array containing the element's x and y coordinates
18192      */
18193     getAnchorXY : function(anchor, local, s){
18194         //Passing a different size is useful for pre-calculating anchors,
18195         //especially for anchored animations that change the el size.
18196         anchor = (anchor || "tl").toLowerCase();
18197         s = s || {};
18198
18199         var me = this,
18200             vp = me.dom == document.body || me.dom == document,
18201             w = s.width || vp ? Ext.core.Element.getViewWidth() : me.getWidth(),
18202             h = s.height || vp ? Ext.core.Element.getViewHeight() : me.getHeight(),
18203             xy,
18204             r = Math.round,
18205             o = me.getXY(),
18206             scroll = me.getScroll(),
18207             extraX = vp ? scroll.left : !local ? o[0] : 0,
18208             extraY = vp ? scroll.top : !local ? o[1] : 0,
18209             hash = {
18210                 c  : [r(w * 0.5), r(h * 0.5)],
18211                 t  : [r(w * 0.5), 0],
18212                 l  : [0, r(h * 0.5)],
18213                 r  : [w, r(h * 0.5)],
18214                 b  : [r(w * 0.5), h],
18215                 tl : [0, 0],
18216                 bl : [0, h],
18217                 br : [w, h],
18218                 tr : [w, 0]
18219             };
18220
18221         xy = hash[anchor];
18222         return [xy[0] + extraX, xy[1] + extraY];
18223     },
18224
18225     /**
18226      * Anchors an element to another element and realigns it when the window is resized.
18227      * @param {Mixed} element The element to align to.
18228      * @param {String} position The position to align to.
18229      * @param {Array} offsets (optional) Offset the positioning by [x, y]
18230      * @param {Boolean/Object} animate (optional) True for the default animation or a standard Element animation config object
18231      * @param {Boolean/Number} monitorScroll (optional) True to monitor body scroll and reposition. If this parameter
18232      * is a number, it is used as the buffer delay (defaults to 50ms).
18233      * @param {Function} callback The function to call after the animation finishes
18234      * @return {Ext.core.Element} this
18235      */
18236     anchorTo : function(el, alignment, offsets, animate, monitorScroll, callback){
18237         var me = this,
18238             dom = me.dom,
18239             scroll = !Ext.isEmpty(monitorScroll),
18240             action = function(){
18241                 Ext.fly(dom).alignTo(el, alignment, offsets, animate);
18242                 Ext.callback(callback, Ext.fly(dom));
18243             },
18244             anchor = this.getAnchor();
18245
18246         // previous listener anchor, remove it
18247         this.removeAnchor();
18248         Ext.apply(anchor, {
18249             fn: action,
18250             scroll: scroll
18251         });
18252
18253         Ext.EventManager.onWindowResize(action, null);
18254
18255         if(scroll){
18256             Ext.EventManager.on(window, 'scroll', action, null,
18257                 {buffer: !isNaN(monitorScroll) ? monitorScroll : 50});
18258         }
18259         action.call(me); // align immediately
18260         return me;
18261     },
18262
18263     /**
18264      * Remove any anchor to this element. See {@link #anchorTo}.
18265      * @return {Ext.core.Element} this
18266      */
18267     removeAnchor : function(){
18268         var me = this,
18269             anchor = this.getAnchor();
18270
18271         if(anchor && anchor.fn){
18272             Ext.EventManager.removeResizeListener(anchor.fn);
18273             if(anchor.scroll){
18274                 Ext.EventManager.un(window, 'scroll', anchor.fn);
18275             }
18276             delete anchor.fn;
18277         }
18278         return me;
18279     },
18280
18281     // private
18282     getAnchor : function(){
18283         var data = Ext.core.Element.data,
18284             dom = this.dom;
18285             if (!dom) {
18286                 return;
18287             }
18288             var anchor = data(dom, '_anchor');
18289
18290         if(!anchor){
18291             anchor = data(dom, '_anchor', {});
18292         }
18293         return anchor;
18294     },
18295
18296     getAlignVector: function(el, spec, offset) {
18297         var me = this,
18298             side = {t:"top", l:"left", r:"right", b: "bottom"},
18299             thisRegion = me.getRegion(),
18300             elRegion;
18301
18302         el = Ext.get(el);
18303         if(!el || !el.dom){
18304             Ext.Error.raise({
18305                 sourceClass: 'Ext.core.Element',
18306                 sourceMethod: 'getAlignVector',
18307                 msg: 'Attempted to align an element that doesn\'t exist'
18308             });
18309         }
18310
18311         elRegion = el.getRegion();
18312     },
18313
18314     /**
18315      * Gets the x,y coordinates to align this element with another element. See {@link #alignTo} for more info on the
18316      * supported position values.
18317      * @param {Mixed} element The element to align to.
18318      * @param {String} position (optional, defaults to "tl-bl?") The position to align to.
18319      * @param {Array} offsets (optional) Offset the positioning by [x, y]
18320      * @return {Array} [x, y]
18321      */
18322     getAlignToXY : function(el, p, o){
18323         el = Ext.get(el);
18324
18325         if(!el || !el.dom){
18326             Ext.Error.raise({
18327                 sourceClass: 'Ext.core.Element',
18328                 sourceMethod: 'getAlignToXY',
18329                 msg: 'Attempted to align an element that doesn\'t exist'
18330             });
18331         }
18332
18333         o = o || [0,0];
18334         p = (!p || p == "?" ? "tl-bl?" : (!(/-/).test(p) && p !== "" ? "tl-" + p : p || "tl-bl")).toLowerCase();
18335
18336         var me = this,
18337             d = me.dom,
18338             a1,
18339             a2,
18340             x,
18341             y,
18342             //constrain the aligned el to viewport if necessary
18343             w,
18344             h,
18345             r,
18346             dw = Ext.core.Element.getViewWidth() -10, // 10px of margin for ie
18347             dh = Ext.core.Element.getViewHeight()-10, // 10px of margin for ie
18348             p1y,
18349             p1x,
18350             p2y,
18351             p2x,
18352             swapY,
18353             swapX,
18354             doc = document,
18355             docElement = doc.documentElement,
18356             docBody = doc.body,
18357             scrollX = (docElement.scrollLeft || docBody.scrollLeft || 0)+5,
18358             scrollY = (docElement.scrollTop || docBody.scrollTop || 0)+5,
18359             c = false, //constrain to viewport
18360             p1 = "",
18361             p2 = "",
18362             m = p.match(/^([a-z]+)-([a-z]+)(\?)?$/);
18363
18364         if(!m){
18365             Ext.Error.raise({
18366                 sourceClass: 'Ext.core.Element',
18367                 sourceMethod: 'getAlignToXY',
18368                 el: el,
18369                 position: p,
18370                 offset: o,
18371                 msg: 'Attemmpted to align an element with an invalid position: "' + p + '"'
18372             });
18373         }
18374
18375         p1 = m[1];
18376         p2 = m[2];
18377         c = !!m[3];
18378
18379         //Subtract the aligned el's internal xy from the target's offset xy
18380         //plus custom offset to get the aligned el's new offset xy
18381         a1 = me.getAnchorXY(p1, true);
18382         a2 = el.getAnchorXY(p2, false);
18383
18384         x = a2[0] - a1[0] + o[0];
18385         y = a2[1] - a1[1] + o[1];
18386
18387         if(c){
18388            w = me.getWidth();
18389            h = me.getHeight();
18390            r = el.getRegion();
18391            //If we are at a viewport boundary and the aligned el is anchored on a target border that is
18392            //perpendicular to the vp border, allow the aligned el to slide on that border,
18393            //otherwise swap the aligned el to the opposite border of the target.
18394            p1y = p1.charAt(0);
18395            p1x = p1.charAt(p1.length-1);
18396            p2y = p2.charAt(0);
18397            p2x = p2.charAt(p2.length-1);
18398            swapY = ((p1y=="t" && p2y=="b") || (p1y=="b" && p2y=="t"));
18399            swapX = ((p1x=="r" && p2x=="l") || (p1x=="l" && p2x=="r"));
18400
18401
18402            if (x + w > dw + scrollX) {
18403                 x = swapX ? r.left-w : dw+scrollX-w;
18404            }
18405            if (x < scrollX) {
18406                x = swapX ? r.right : scrollX;
18407            }
18408            if (y + h > dh + scrollY) {
18409                 y = swapY ? r.top-h : dh+scrollY-h;
18410             }
18411            if (y < scrollY){
18412                y = swapY ? r.bottom : scrollY;
18413            }
18414         }
18415         return [x,y];
18416     },
18417
18418     /**
18419      * Aligns this element with another element relative to the specified anchor points. If the other element is the
18420      * document it aligns it to the viewport.
18421      * The position parameter is optional, and can be specified in any one of the following formats:
18422      * <ul>
18423      *   <li><b>Blank</b>: Defaults to aligning the element's top-left corner to the target's bottom-left corner ("tl-bl").</li>
18424      *   <li><b>One anchor (deprecated)</b>: The passed anchor position is used as the target element's anchor point.
18425      *       The element being aligned will position its top-left corner (tl) to that point.  <i>This method has been
18426      *       deprecated in favor of the newer two anchor syntax below</i>.</li>
18427      *   <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
18428      *       element's anchor point, and the second value is used as the target's anchor point.</li>
18429      * </ul>
18430      * In addition to the anchor points, the position parameter also supports the "?" character.  If "?" is passed at the end of
18431      * the position string, the element will attempt to align as specified, but the position will be adjusted to constrain to
18432      * the viewport if necessary.  Note that the element being aligned might be swapped to align to a different position than
18433      * that specified in order to enforce the viewport constraints.
18434      * Following are all of the supported anchor positions:
18435 <pre>
18436 Value  Description
18437 -----  -----------------------------
18438 tl     The top left corner (default)
18439 t      The center of the top edge
18440 tr     The top right corner
18441 l      The center of the left edge
18442 c      In the center of the element
18443 r      The center of the right edge
18444 bl     The bottom left corner
18445 b      The center of the bottom edge
18446 br     The bottom right corner
18447 </pre>
18448 Example Usage:
18449 <pre><code>
18450 // align el to other-el using the default positioning ("tl-bl", non-constrained)
18451 el.alignTo("other-el");
18452
18453 // align the top left corner of el with the top right corner of other-el (constrained to viewport)
18454 el.alignTo("other-el", "tr?");
18455
18456 // align the bottom right corner of el with the center left edge of other-el
18457 el.alignTo("other-el", "br-l?");
18458
18459 // align the center of el with the bottom left corner of other-el and
18460 // adjust the x position by -6 pixels (and the y position by 0)
18461 el.alignTo("other-el", "c-bl", [-6, 0]);
18462 </code></pre>
18463      * @param {Mixed} element The element to align to.
18464      * @param {String} position (optional, defaults to "tl-bl?") The position to align to.
18465      * @param {Array} offsets (optional) Offset the positioning by [x, y]
18466      * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
18467      * @return {Ext.core.Element} this
18468      */
18469     alignTo : function(element, position, offsets, animate){
18470         var me = this;
18471         return me.setXY(me.getAlignToXY(element, position, offsets),
18472                         me.anim && !!animate ? me.anim(animate) : false);
18473     },
18474
18475     // private ==>  used outside of core
18476     adjustForConstraints : function(xy, parent) {
18477         var vector = this.getConstrainVector(parent, xy);
18478         if (vector) {
18479             xy[0] += vector[0];
18480             xy[1] += vector[1];
18481         }
18482         return xy;
18483     },
18484
18485     /**
18486      * <p>Returns the <code>[X, Y]</code> vector by which this element must be translated to make a best attempt
18487      * to constrain within the passed constraint. Returns <code>false</code> is this element does not need to be moved.</p>
18488      * <p>Priority is given to constraining the top and left within the constraint.</p>
18489      * <p>The constraint may either be an existing element into which this element is to be constrained, or
18490      * an {@link Ext.util.Region Region} into which this element is to be constrained.</p>
18491      * @param constrainTo {Mixed} The Element or {@link Ext.util.Region Region} into which this element is to be constrained.
18492      * @param proposedPosition {Array} A proposed <code>[X, Y]</code> position to test for validity and to produce a vector for instead
18493      * of using this Element's current position;
18494      * @returns {Array} <b>If</b> this element <i>needs</i> to be translated, an <code>[X, Y]</code>
18495      * vector by which this element must be translated. Otherwise, <code>false</code>.
18496      */
18497     getConstrainVector: function(constrainTo, proposedPosition) {
18498         if (!(constrainTo instanceof Ext.util.Region)) {
18499             constrainTo = Ext.get(constrainTo).getViewRegion();
18500         }
18501         var thisRegion = this.getRegion(),
18502             vector = [0, 0],
18503             shadowSize = this.shadow && this.shadow.offset,
18504             overflowed = false;
18505
18506         // Shift this region to occupy the proposed position
18507         if (proposedPosition) {
18508             thisRegion.translateBy(proposedPosition[0] - thisRegion.x, proposedPosition[1] - thisRegion.y);
18509         }
18510
18511         // Reduce the constrain region to allow for shadow
18512         // TODO: Rewrite the Shadow class. When that's done, get the extra for each side from the Shadow.
18513         if (shadowSize) {
18514             constrainTo.adjust(0, -shadowSize, -shadowSize, shadowSize);
18515         }
18516
18517         // Constrain the X coordinate by however much this Element overflows
18518         if (thisRegion.right > constrainTo.right) {
18519             overflowed = true;
18520             vector[0] = (constrainTo.right - thisRegion.right);    // overflowed the right
18521         }
18522         if (thisRegion.left + vector[0] < constrainTo.left) {
18523             overflowed = true;
18524             vector[0] = (constrainTo.left - thisRegion.left);      // overflowed the left
18525         }
18526
18527         // Constrain the Y coordinate by however much this Element overflows
18528         if (thisRegion.bottom > constrainTo.bottom) {
18529             overflowed = true;
18530             vector[1] = (constrainTo.bottom - thisRegion.bottom);  // overflowed the bottom
18531         }
18532         if (thisRegion.top + vector[1] < constrainTo.top) {
18533             overflowed = true;
18534             vector[1] = (constrainTo.top - thisRegion.top);        // overflowed the top
18535         }
18536         return overflowed ? vector : false;
18537     },
18538
18539     /**
18540     * Calculates the x, y to center this element on the screen
18541     * @return {Array} The x, y values [x, y]
18542     */
18543     getCenterXY : function(){
18544         return this.getAlignToXY(document, 'c-c');
18545     },
18546
18547     /**
18548     * Centers the Element in either the viewport, or another Element.
18549     * @param {Mixed} centerIn (optional) The element in which to center the element.
18550     */
18551     center : function(centerIn){
18552         return this.alignTo(centerIn || document, 'c-c');
18553     }
18554 });
18555
18556 /**
18557  * @class Ext.core.Element
18558  */
18559 (function(){
18560
18561 var ELEMENT = Ext.core.Element,
18562     LEFT = "left",
18563     RIGHT = "right",
18564     TOP = "top",
18565     BOTTOM = "bottom",
18566     POSITION = "position",
18567     STATIC = "static",
18568     RELATIVE = "relative",
18569     AUTO = "auto",
18570     ZINDEX = "z-index";
18571
18572 Ext.override(Ext.core.Element, {
18573     /**
18574       * 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).
18575       * @return {Number} The X position of the element
18576       */
18577     getX : function(){
18578         return ELEMENT.getX(this.dom);
18579     },
18580
18581     /**
18582       * 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).
18583       * @return {Number} The Y position of the element
18584       */
18585     getY : function(){
18586         return ELEMENT.getY(this.dom);
18587     },
18588
18589     /**
18590       * 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).
18591       * @return {Array} The XY position of the element
18592       */
18593     getXY : function(){
18594         return ELEMENT.getXY(this.dom);
18595     },
18596
18597     /**
18598       * 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.
18599       * @param {Mixed} element The element to get the offsets from.
18600       * @return {Array} The XY page offsets (e.g. [100, -200])
18601       */
18602     getOffsetsTo : function(el){
18603         var o = this.getXY(),
18604             e = Ext.fly(el, '_internal').getXY();
18605         return [o[0]-e[0],o[1]-e[1]];
18606     },
18607
18608     /**
18609      * 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).
18610      * @param {Number} The X position of the element
18611      * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
18612      * @return {Ext.core.Element} this
18613      */
18614     setX : function(x, animate){
18615         return this.setXY([x, this.getY()], animate);
18616     },
18617
18618     /**
18619      * 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).
18620      * @param {Number} The Y position of the element
18621      * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
18622      * @return {Ext.core.Element} this
18623      */
18624     setY : function(y, animate){
18625         return this.setXY([this.getX(), y], animate);
18626     },
18627
18628     /**
18629      * Sets the element's left position directly using CSS style (instead of {@link #setX}).
18630      * @param {String} left The left CSS property value
18631      * @return {Ext.core.Element} this
18632      */
18633     setLeft : function(left){
18634         this.setStyle(LEFT, this.addUnits(left));
18635         return this;
18636     },
18637
18638     /**
18639      * Sets the element's top position directly using CSS style (instead of {@link #setY}).
18640      * @param {String} top The top CSS property value
18641      * @return {Ext.core.Element} this
18642      */
18643     setTop : function(top){
18644         this.setStyle(TOP, this.addUnits(top));
18645         return this;
18646     },
18647
18648     /**
18649      * Sets the element's CSS right style.
18650      * @param {String} right The right CSS property value
18651      * @return {Ext.core.Element} this
18652      */
18653     setRight : function(right){
18654         this.setStyle(RIGHT, this.addUnits(right));
18655         return this;
18656     },
18657
18658     /**
18659      * Sets the element's CSS bottom style.
18660      * @param {String} bottom The bottom CSS property value
18661      * @return {Ext.core.Element} this
18662      */
18663     setBottom : function(bottom){
18664         this.setStyle(BOTTOM, this.addUnits(bottom));
18665         return this;
18666     },
18667
18668     /**
18669      * Sets the position of the element in page coordinates, regardless of how the element is positioned.
18670      * The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
18671      * @param {Array} pos Contains X & Y [x, y] values for new position (coordinates are page-based)
18672      * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
18673      * @return {Ext.core.Element} this
18674      */
18675     setXY: function(pos, animate) {
18676         var me = this;
18677         if (!animate || !me.anim) {
18678             ELEMENT.setXY(me.dom, pos);
18679         }
18680         else {
18681             if (!Ext.isObject(animate)) {
18682                 animate = {};
18683             }
18684             me.animate(Ext.applyIf({ to: { x: pos[0], y: pos[1] } }, animate));
18685         }
18686         return me;
18687     },
18688
18689     /**
18690      * Sets the position of the element in page coordinates, regardless of how the element is positioned.
18691      * The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
18692      * @param {Number} x X value for new position (coordinates are page-based)
18693      * @param {Number} y Y value for new position (coordinates are page-based)
18694      * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
18695      * @return {Ext.core.Element} this
18696      */
18697     setLocation : function(x, y, animate){
18698         return this.setXY([x, y], animate);
18699     },
18700
18701     /**
18702      * Sets the position of the element in page coordinates, regardless of how the element is positioned.
18703      * The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
18704      * @param {Number} x X value for new position (coordinates are page-based)
18705      * @param {Number} y Y value for new position (coordinates are page-based)
18706      * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
18707      * @return {Ext.core.Element} this
18708      */
18709     moveTo : function(x, y, animate){
18710         return this.setXY([x, y], animate);
18711     },
18712
18713     /**
18714      * Gets the left X coordinate
18715      * @param {Boolean} local True to get the local css position instead of page coordinate
18716      * @return {Number}
18717      */
18718     getLeft : function(local){
18719         return !local ? this.getX() : parseInt(this.getStyle(LEFT), 10) || 0;
18720     },
18721
18722     /**
18723      * Gets the right X coordinate of the element (element X position + element width)
18724      * @param {Boolean} local True to get the local css position instead of page coordinate
18725      * @return {Number}
18726      */
18727     getRight : function(local){
18728         var me = this;
18729         return !local ? me.getX() + me.getWidth() : (me.getLeft(true) + me.getWidth()) || 0;
18730     },
18731
18732     /**
18733      * Gets the top Y coordinate
18734      * @param {Boolean} local True to get the local css position instead of page coordinate
18735      * @return {Number}
18736      */
18737     getTop : function(local) {
18738         return !local ? this.getY() : parseInt(this.getStyle(TOP), 10) || 0;
18739     },
18740
18741     /**
18742      * Gets the bottom Y coordinate of the element (element Y position + element height)
18743      * @param {Boolean} local True to get the local css position instead of page coordinate
18744      * @return {Number}
18745      */
18746     getBottom : function(local){
18747         var me = this;
18748         return !local ? me.getY() + me.getHeight() : (me.getTop(true) + me.getHeight()) || 0;
18749     },
18750
18751     /**
18752     * Initializes positioning on this element. If a desired position is not passed, it will make the
18753     * the element positioned relative IF it is not already positioned.
18754     * @param {String} pos (optional) Positioning to use "relative", "absolute" or "fixed"
18755     * @param {Number} zIndex (optional) The zIndex to apply
18756     * @param {Number} x (optional) Set the page X position
18757     * @param {Number} y (optional) Set the page Y position
18758     */
18759     position : function(pos, zIndex, x, y) {
18760         var me = this;
18761
18762         if (!pos && me.isStyle(POSITION, STATIC)){
18763             me.setStyle(POSITION, RELATIVE);
18764         } else if(pos) {
18765             me.setStyle(POSITION, pos);
18766         }
18767         if (zIndex){
18768             me.setStyle(ZINDEX, zIndex);
18769         }
18770         if (x || y) {
18771             me.setXY([x || false, y || false]);
18772         }
18773     },
18774
18775     /**
18776     * Clear positioning back to the default when the document was loaded
18777     * @param {String} value (optional) The value to use for the left,right,top,bottom, defaults to '' (empty string). You could use 'auto'.
18778     * @return {Ext.core.Element} this
18779      */
18780     clearPositioning : function(value){
18781         value = value || '';
18782         this.setStyle({
18783             left : value,
18784             right : value,
18785             top : value,
18786             bottom : value,
18787             "z-index" : "",
18788             position : STATIC
18789         });
18790         return this;
18791     },
18792
18793     /**
18794     * Gets an object with all CSS positioning properties. Useful along with setPostioning to get
18795     * snapshot before performing an update and then restoring the element.
18796     * @return {Object}
18797     */
18798     getPositioning : function(){
18799         var l = this.getStyle(LEFT);
18800         var t = this.getStyle(TOP);
18801         return {
18802             "position" : this.getStyle(POSITION),
18803             "left" : l,
18804             "right" : l ? "" : this.getStyle(RIGHT),
18805             "top" : t,
18806             "bottom" : t ? "" : this.getStyle(BOTTOM),
18807             "z-index" : this.getStyle(ZINDEX)
18808         };
18809     },
18810
18811     /**
18812     * Set positioning with an object returned by getPositioning().
18813     * @param {Object} posCfg
18814     * @return {Ext.core.Element} this
18815      */
18816     setPositioning : function(pc){
18817         var me = this,
18818             style = me.dom.style;
18819
18820         me.setStyle(pc);
18821
18822         if(pc.right == AUTO){
18823             style.right = "";
18824         }
18825         if(pc.bottom == AUTO){
18826             style.bottom = "";
18827         }
18828
18829         return me;
18830     },
18831
18832     /**
18833      * Translates the passed page coordinates into left/top css values for this element
18834      * @param {Number/Array} x The page x or an array containing [x, y]
18835      * @param {Number} y (optional) The page y, required if x is not an array
18836      * @return {Object} An object with left and top properties. e.g. {left: (value), top: (value)}
18837      */
18838     translatePoints: function(x, y) {
18839         if (Ext.isArray(x)) {
18840              y = x[1];
18841              x = x[0];
18842         }
18843         var me = this,
18844             relative = me.isStyle(POSITION, RELATIVE),
18845             o = me.getXY(),
18846             left = parseInt(me.getStyle(LEFT), 10),
18847             top = parseInt(me.getStyle(TOP), 10);
18848
18849         if (!Ext.isNumber(left)) {
18850             left = relative ? 0 : me.dom.offsetLeft;
18851         }
18852         if (!Ext.isNumber(top)) {
18853             top = relative ? 0 : me.dom.offsetTop;
18854         }
18855         left = (Ext.isNumber(x)) ? x - o[0] + left : undefined;
18856         top = (Ext.isNumber(y)) ? y - o[1] + top : undefined;
18857         return {
18858             left: left,
18859             top: top
18860         };
18861     },
18862
18863     /**
18864      * 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.
18865      * @param {Object} box The box to fill {x, y, width, height}
18866      * @param {Boolean} adjust (optional) Whether to adjust for box-model issues automatically
18867      * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
18868      * @return {Ext.core.Element} this
18869      */
18870     setBox: function(box, adjust, animate) {
18871         var me = this,
18872             w = box.width,
18873             h = box.height;
18874         if ((adjust && !me.autoBoxAdjust) && !me.isBorderBox()) {
18875             w -= (me.getBorderWidth("lr") + me.getPadding("lr"));
18876             h -= (me.getBorderWidth("tb") + me.getPadding("tb"));
18877         }
18878         me.setBounds(box.x, box.y, w, h, animate);
18879         return me;
18880     },
18881
18882     /**
18883      * Return an object defining the area of this Element which can be passed to {@link #setBox} to
18884      * set another Element's size/location to match this element.
18885      * @param {Boolean} contentBox (optional) If true a box for the content of the element is returned.
18886      * @param {Boolean} local (optional) If true the element's left and top are returned instead of page x/y.
18887      * @return {Object} box An object in the format<pre><code>
18888 {
18889     x: &lt;Element's X position>,
18890     y: &lt;Element's Y position>,
18891     width: &lt;Element's width>,
18892     height: &lt;Element's height>,
18893     bottom: &lt;Element's lower bound>,
18894     right: &lt;Element's rightmost bound>
18895 }
18896 </code></pre>
18897      * The returned object may also be addressed as an Array where index 0 contains the X position
18898      * and index 1 contains the Y position. So the result may also be used for {@link #setXY}
18899      */
18900     getBox: function(contentBox, local) {
18901         var me = this,
18902             xy,
18903             left,
18904             top,
18905             getBorderWidth = me.getBorderWidth,
18906             getPadding = me.getPadding,
18907             l, r, t, b, w, h, bx;
18908         if (!local) {
18909             xy = me.getXY();
18910         } else {
18911             left = parseInt(me.getStyle("left"), 10) || 0;
18912             top = parseInt(me.getStyle("top"), 10) || 0;
18913             xy = [left, top];
18914         }
18915         w = me.getWidth();
18916         h = me.getHeight();
18917         if (!contentBox) {
18918             bx = {
18919                 x: xy[0],
18920                 y: xy[1],
18921                 0: xy[0],
18922                 1: xy[1],
18923                 width: w,
18924                 height: h
18925             };
18926         } else {
18927             l = getBorderWidth.call(me, "l") + getPadding.call(me, "l");
18928             r = getBorderWidth.call(me, "r") + getPadding.call(me, "r");
18929             t = getBorderWidth.call(me, "t") + getPadding.call(me, "t");
18930             b = getBorderWidth.call(me, "b") + getPadding.call(me, "b");
18931             bx = {
18932                 x: xy[0] + l,
18933                 y: xy[1] + t,
18934                 0: xy[0] + l,
18935                 1: xy[1] + t,
18936                 width: w - (l + r),
18937                 height: h - (t + b)
18938             };
18939         }
18940         bx.right = bx.x + bx.width;
18941         bx.bottom = bx.y + bx.height;
18942         return bx;
18943     },
18944
18945     /**
18946      * Move this element relative to its current position.
18947      * @param {String} direction Possible values are: "l" (or "left"), "r" (or "right"), "t" (or "top", or "up"), "b" (or "bottom", or "down").
18948      * @param {Number} distance How far to move the element in pixels
18949      * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
18950      * @return {Ext.core.Element} this
18951      */
18952     move: function(direction, distance, animate) {
18953         var me = this,
18954             xy = me.getXY(),
18955             x = xy[0],
18956             y = xy[1],
18957             left = [x - distance, y],
18958             right = [x + distance, y],
18959             top = [x, y - distance],
18960             bottom = [x, y + distance],
18961             hash = {
18962                 l: left,
18963                 left: left,
18964                 r: right,
18965                 right: right,
18966                 t: top,
18967                 top: top,
18968                 up: top,
18969                 b: bottom,
18970                 bottom: bottom,
18971                 down: bottom
18972             };
18973
18974         direction = direction.toLowerCase();
18975         me.moveTo(hash[direction][0], hash[direction][1], animate);
18976     },
18977
18978     /**
18979      * Quick set left and top adding default units
18980      * @param {String} left The left CSS property value
18981      * @param {String} top The top CSS property value
18982      * @return {Ext.core.Element} this
18983      */
18984     setLeftTop: function(left, top) {
18985         var me = this,
18986             style = me.dom.style;
18987         style.left = me.addUnits(left);
18988         style.top = me.addUnits(top);
18989         return me;
18990     },
18991
18992     /**
18993      * Returns the region of this element.
18994      * The element must be part of the DOM tree to have a region (display:none or elements not appended return false).
18995      * @return {Region} A Ext.util.Region containing "top, left, bottom, right" member data.
18996      */
18997     getRegion: function() {
18998         return this.getPageBox(true);
18999     },
19000
19001     /**
19002      * Returns the <b>content</b> region of this element. That is the region within the borders and padding.
19003      * @return {Region} A Ext.util.Region containing "top, left, bottom, right" member data.
19004      */
19005     getViewRegion: function() {
19006         var me = this,
19007             isBody = me.dom === document.body,
19008             scroll, pos, top, left, width, height;
19009             
19010         // For the body we want to do some special logic
19011         if (isBody) {
19012             scroll = me.getScroll();
19013             left = scroll.left;
19014             top = scroll.top;
19015             width = Ext.core.Element.getViewportWidth();
19016             height = Ext.core.Element.getViewportHeight();
19017         }
19018         else {
19019             pos = me.getXY();
19020             left = pos[0] + me.getBorderWidth('l') + me.getPadding('l');
19021             top = pos[1] + me.getBorderWidth('t') + me.getPadding('t');
19022             width = me.getWidth(true);
19023             height = me.getHeight(true);
19024         }
19025
19026         return Ext.create('Ext.util.Region', top, left + width, top + height, left);
19027     },
19028
19029     /**
19030      * Return an object defining the area of this Element which can be passed to {@link #setBox} to
19031      * set another Element's size/location to match this element.
19032      * @param {Boolean} asRegion(optional) If true an Ext.util.Region will be returned
19033      * @return {Object} box An object in the format<pre><code>
19034 {
19035     x: &lt;Element's X position>,
19036     y: &lt;Element's Y position>,
19037     width: &lt;Element's width>,
19038     height: &lt;Element's height>,
19039     bottom: &lt;Element's lower bound>,
19040     right: &lt;Element's rightmost bound>
19041 }
19042 </code></pre>
19043      * The returned object may also be addressed as an Array where index 0 contains the X position
19044      * and index 1 contains the Y position. So the result may also be used for {@link #setXY}
19045      */
19046     getPageBox : function(getRegion) {
19047         var me = this,
19048             el = me.dom,
19049             isDoc = el === document.body,
19050             w = isDoc ? Ext.core.Element.getViewWidth()  : el.offsetWidth,
19051             h = isDoc ? Ext.core.Element.getViewHeight() : el.offsetHeight,
19052             xy = me.getXY(),
19053             t = xy[1],
19054             r = xy[0] + w,
19055             b = xy[1] + h,
19056             l = xy[0];
19057
19058         if (getRegion) {
19059             return Ext.create('Ext.util.Region', t, r, b, l);
19060         }
19061         else {
19062             return {
19063                 left: l,
19064                 top: t,
19065                 width: w,
19066                 height: h,
19067                 right: r,
19068                 bottom: b
19069             };
19070         }
19071     },
19072
19073     /**
19074      * Sets the element's position and size in one shot. If animation is true then width, height, x and y will be animated concurrently.
19075      * @param {Number} x X value for new position (coordinates are page-based)
19076      * @param {Number} y Y value for new position (coordinates are page-based)
19077      * @param {Mixed} width The new width. This may be one of:<div class="mdetail-params"><ul>
19078      * <li>A Number specifying the new width in this Element's {@link #defaultUnit}s (by default, pixels)</li>
19079      * <li>A String used to set the CSS width style. Animation may <b>not</b> be used.
19080      * </ul></div>
19081      * @param {Mixed} height The new height. This may be one of:<div class="mdetail-params"><ul>
19082      * <li>A Number specifying the new height in this Element's {@link #defaultUnit}s (by default, pixels)</li>
19083      * <li>A String used to set the CSS height style. Animation may <b>not</b> be used.</li>
19084      * </ul></div>
19085      * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
19086      * @return {Ext.core.Element} this
19087      */
19088     setBounds: function(x, y, width, height, animate) {
19089         var me = this;
19090         if (!animate || !me.anim) {
19091             me.setSize(width, height);
19092             me.setLocation(x, y);
19093         } else {
19094             if (!Ext.isObject(animate)) {
19095                 animate = {};
19096             }
19097             me.animate(Ext.applyIf({
19098                 to: {
19099                     x: x,
19100                     y: y,
19101                     width: me.adjustWidth(width),
19102                     height: me.adjustHeight(height)
19103                 }
19104             }, animate));
19105         }
19106         return me;
19107     },
19108
19109     /**
19110      * Sets the element's position and size the specified region. If animation is true then width, height, x and y will be animated concurrently.
19111      * @param {Ext.util.Region} region The region to fill
19112      * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
19113      * @return {Ext.core.Element} this
19114      */
19115     setRegion: function(region, animate) {
19116         return this.setBounds(region.left, region.top, region.right - region.left, region.bottom - region.top, animate);
19117     }
19118 });
19119 })();
19120
19121 /**
19122  * @class Ext.core.Element
19123  */
19124 Ext.override(Ext.core.Element, {
19125     /**
19126      * Returns true if this element is scrollable.
19127      * @return {Boolean}
19128      */
19129     isScrollable : function(){
19130         var dom = this.dom;
19131         return dom.scrollHeight > dom.clientHeight || dom.scrollWidth > dom.clientWidth;
19132     },
19133
19134     /**
19135      * Returns the current scroll position of the element.
19136      * @return {Object} An object containing the scroll position in the format {left: (scrollLeft), top: (scrollTop)}
19137      */
19138     getScroll : function() {
19139         var d = this.dom, 
19140             doc = document,
19141             body = doc.body,
19142             docElement = doc.documentElement,
19143             l,
19144             t,
19145             ret;
19146
19147         if (d == doc || d == body) {
19148             if (Ext.isIE && Ext.isStrict) {
19149                 l = docElement.scrollLeft; 
19150                 t = docElement.scrollTop;
19151             } else {
19152                 l = window.pageXOffset;
19153                 t = window.pageYOffset;
19154             }
19155             ret = {
19156                 left: l || (body ? body.scrollLeft : 0), 
19157                 top : t || (body ? body.scrollTop : 0)
19158             };
19159         } else {
19160             ret = {
19161                 left: d.scrollLeft, 
19162                 top : d.scrollTop
19163             };
19164         }
19165         
19166         return ret;
19167     },
19168     
19169     /**
19170      * 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().
19171      * @param {String} side Either "left" for scrollLeft values or "top" for scrollTop values.
19172      * @param {Number} value The new scroll value
19173      * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
19174      * @return {Element} this
19175      */
19176     scrollTo : function(side, value, animate) {
19177         //check if we're scrolling top or left
19178         var top = /top/i.test(side),
19179             me = this,
19180             dom = me.dom,
19181             obj = {},
19182             prop;
19183         if (!animate || !me.anim) {
19184             // just setting the value, so grab the direction
19185             prop = 'scroll' + (top ? 'Top' : 'Left');
19186             dom[prop] = value;
19187         }
19188         else {
19189             if (!Ext.isObject(animate)) {
19190                 animate = {};
19191             }
19192             obj['scroll' + (top ? 'Top' : 'Left')] = value;
19193             me.animate(Ext.applyIf({
19194                 to: obj
19195             }, animate));
19196         }
19197         return me;
19198     },
19199
19200     /**
19201      * Scrolls this element into view within the passed container.
19202      * @param {Mixed} container (optional) The container element to scroll (defaults to document.body).  Should be a
19203      * string (id), dom node, or Ext.core.Element.
19204      * @param {Boolean} hscroll (optional) False to disable horizontal scroll (defaults to true)
19205      * @return {Ext.core.Element} this
19206      */
19207     scrollIntoView : function(container, hscroll) {
19208         container = Ext.getDom(container) || Ext.getBody().dom;
19209         var el = this.dom,
19210             offsets = this.getOffsetsTo(container),
19211             // el's box
19212             left = offsets[0] + container.scrollLeft,
19213             top = offsets[1] + container.scrollTop,
19214             bottom = top + el.offsetHeight,
19215             right = left + el.offsetWidth,
19216             // ct's box
19217             ctClientHeight = container.clientHeight,
19218             ctScrollTop = parseInt(container.scrollTop, 10),
19219             ctScrollLeft = parseInt(container.scrollLeft, 10),
19220             ctBottom = ctScrollTop + ctClientHeight,
19221             ctRight = ctScrollLeft + container.clientWidth;
19222
19223         if (el.offsetHeight > ctClientHeight || top < ctScrollTop) {
19224             container.scrollTop = top;
19225         } else if (bottom > ctBottom) {
19226             container.scrollTop = bottom - ctClientHeight;
19227         }
19228         // corrects IE, other browsers will ignore
19229         container.scrollTop = container.scrollTop;
19230
19231         if (hscroll !== false) {
19232             if (el.offsetWidth > container.clientWidth || left < ctScrollLeft) {
19233                 container.scrollLeft = left;
19234             }
19235             else if (right > ctRight) {
19236                 container.scrollLeft = right - container.clientWidth;
19237             }
19238             container.scrollLeft = container.scrollLeft;
19239         }
19240         return this;
19241     },
19242
19243     // private
19244     scrollChildIntoView : function(child, hscroll) {
19245         Ext.fly(child, '_scrollChildIntoView').scrollIntoView(this, hscroll);
19246     },
19247
19248     /**
19249      * Scrolls this element the specified direction. Does bounds checking to make sure the scroll is
19250      * within this element's scrollable range.
19251      * @param {String} direction Possible values are: "l" (or "left"), "r" (or "right"), "t" (or "top", or "up"), "b" (or "bottom", or "down").
19252      * @param {Number} distance How far to scroll the element in pixels
19253      * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
19254      * @return {Boolean} Returns true if a scroll was triggered or false if the element
19255      * was scrolled as far as it could go.
19256      */
19257      scroll : function(direction, distance, animate) {
19258         if (!this.isScrollable()) {
19259             return false;
19260         }
19261         var el = this.dom,
19262             l = el.scrollLeft, t = el.scrollTop,
19263             w = el.scrollWidth, h = el.scrollHeight,
19264             cw = el.clientWidth, ch = el.clientHeight,
19265             scrolled = false, v,
19266             hash = {
19267                 l: Math.min(l + distance, w-cw),
19268                 r: v = Math.max(l - distance, 0),
19269                 t: Math.max(t - distance, 0),
19270                 b: Math.min(t + distance, h-ch)
19271             };
19272             hash.d = hash.b;
19273             hash.u = hash.t;
19274
19275         direction = direction.substr(0, 1);
19276         if ((v = hash[direction]) > -1) {
19277             scrolled = true;
19278             this.scrollTo(direction == 'l' || direction == 'r' ? 'left' : 'top', v, this.anim(animate));
19279         }
19280         return scrolled;
19281     }
19282 });
19283 /**
19284  * @class Ext.core.Element
19285  */
19286 Ext.core.Element.addMethods(
19287     function() {
19288         var VISIBILITY      = "visibility",
19289             DISPLAY         = "display",
19290             HIDDEN          = "hidden",
19291             NONE            = "none",
19292             XMASKED         = Ext.baseCSSPrefix + "masked",
19293             XMASKEDRELATIVE = Ext.baseCSSPrefix + "masked-relative",
19294             data            = Ext.core.Element.data;
19295
19296         return {
19297             /**
19298              * Checks whether the element is currently visible using both visibility and display properties.
19299              * @param {Boolean} deep (optional) True to walk the dom and see if parent elements are hidden (defaults to false)
19300              * @return {Boolean} True if the element is currently visible, else false
19301              */
19302             isVisible : function(deep) {
19303                 var vis = !this.isStyle(VISIBILITY, HIDDEN) && !this.isStyle(DISPLAY, NONE),
19304                     p   = this.dom.parentNode;
19305
19306                 if (deep !== true || !vis) {
19307                     return vis;
19308                 }
19309
19310                 while (p && !(/^body/i.test(p.tagName))) {
19311                     if (!Ext.fly(p, '_isVisible').isVisible()) {
19312                         return false;
19313                     }
19314                     p = p.parentNode;
19315                 }
19316                 return true;
19317             },
19318
19319             /**
19320              * Returns true if display is not "none"
19321              * @return {Boolean}
19322              */
19323             isDisplayed : function() {
19324                 return !this.isStyle(DISPLAY, NONE);
19325             },
19326
19327             /**
19328              * Convenience method for setVisibilityMode(Element.DISPLAY)
19329              * @param {String} display (optional) What to set display to when visible
19330              * @return {Ext.core.Element} this
19331              */
19332             enableDisplayMode : function(display) {
19333                 this.setVisibilityMode(Ext.core.Element.DISPLAY);
19334
19335                 if (!Ext.isEmpty(display)) {
19336                     data(this.dom, 'originalDisplay', display);
19337                 }
19338
19339                 return this;
19340             },
19341
19342             /**
19343              * Puts a mask over this element to disable user interaction. Requires core.css.
19344              * This method can only be applied to elements which accept child nodes.
19345              * @param {String} msg (optional) A message to display in the mask
19346              * @param {String} msgCls (optional) A css class to apply to the msg element
19347              * @return {Element} The mask element
19348              */
19349             mask : function(msg, msgCls) {
19350                 var me  = this,
19351                     dom = me.dom,
19352                     setExpression = dom.style.setExpression,
19353                     dh  = Ext.core.DomHelper,
19354                     EXTELMASKMSG = Ext.baseCSSPrefix + "mask-msg",
19355                     el,
19356                     mask;
19357
19358                 if (!(/^body/i.test(dom.tagName) && me.getStyle('position') == 'static')) {
19359                     me.addCls(XMASKEDRELATIVE);
19360                 }
19361                 el = data(dom, 'maskMsg');
19362                 if (el) {
19363                     el.remove();
19364                 }
19365                 el = data(dom, 'mask');
19366                 if (el) {
19367                     el.remove();
19368                 }
19369
19370                 mask = dh.append(dom, {cls : Ext.baseCSSPrefix + "mask"}, true);
19371                 data(dom, 'mask', mask);
19372
19373                 me.addCls(XMASKED);
19374                 mask.setDisplayed(true);
19375
19376                 if (typeof msg == 'string') {
19377                     var mm = dh.append(dom, {cls : EXTELMASKMSG, cn:{tag:'div'}}, true);
19378                     data(dom, 'maskMsg', mm);
19379                     mm.dom.className = msgCls ? EXTELMASKMSG + " " + msgCls : EXTELMASKMSG;
19380                     mm.dom.firstChild.innerHTML = msg;
19381                     mm.setDisplayed(true);
19382                     mm.center(me);
19383                 }
19384                 // NOTE: CSS expressions are resource intensive and to be used only as a last resort
19385                 // These expressions are removed as soon as they are no longer necessary - in the unmask method.
19386                 // In normal use cases an element will be masked for a limited period of time.
19387                 // Fix for https://sencha.jira.com/browse/EXTJSIV-19.
19388                 // IE6 strict mode and IE6-9 quirks mode takes off left+right padding when calculating width!
19389                 if (!Ext.supports.IncludePaddingInWidthCalculation && setExpression) {
19390                     mask.dom.style.setExpression('width', 'this.parentNode.offsetWidth + "px"');
19391                 }
19392
19393                 // Some versions and modes of IE subtract top+bottom padding when calculating height.
19394                 // Different versions from those which make the same error for width!
19395                 if (!Ext.supports.IncludePaddingInHeightCalculation && setExpression) {
19396                     mask.dom.style.setExpression('height', 'this.parentNode.offsetHeight + "px"');
19397                 }
19398                 // ie will not expand full height automatically
19399                 else if (Ext.isIE && !(Ext.isIE7 && Ext.isStrict) && me.getStyle('height') == 'auto') {
19400                     mask.setSize(undefined, me.getHeight());
19401                 }
19402                 return mask;
19403             },
19404
19405             /**
19406              * Removes a previously applied mask.
19407              */
19408             unmask : function() {
19409                 var me      = this,
19410                     dom     = me.dom,
19411                     mask    = data(dom, 'mask'),
19412                     maskMsg = data(dom, 'maskMsg');
19413
19414                 if (mask) {
19415                     // Remove resource-intensive CSS expressions as soon as they are not required.
19416                     if (mask.dom.style.clearExpression) {
19417                         mask.dom.style.clearExpression('width');
19418                         mask.dom.style.clearExpression('height');
19419                     }
19420                     if (maskMsg) {
19421                         maskMsg.remove();
19422                         data(dom, 'maskMsg', undefined);
19423                     }
19424
19425                     mask.remove();
19426                     data(dom, 'mask', undefined);
19427                     me.removeCls([XMASKED, XMASKEDRELATIVE]);
19428                 }
19429             },
19430             /**
19431              * Returns true if this element is masked. Also re-centers any displayed message within the mask.
19432              * @return {Boolean}
19433              */
19434             isMasked : function() {
19435                 var me = this,
19436                     mask = data(me.dom, 'mask'),
19437                     maskMsg = data(me.dom, 'maskMsg');
19438
19439                 if (mask && mask.isVisible()) {
19440                     if (maskMsg) {
19441                         maskMsg.center(me);
19442                     }
19443                     return true;
19444                 }
19445                 return false;
19446             },
19447
19448             /**
19449              * Creates an iframe shim for this element to keep selects and other windowed objects from
19450              * showing through.
19451              * @return {Ext.core.Element} The new shim element
19452              */
19453             createShim : function() {
19454                 var el = document.createElement('iframe'),
19455                     shim;
19456
19457                 el.frameBorder = '0';
19458                 el.className = Ext.baseCSSPrefix + 'shim';
19459                 el.src = Ext.SSL_SECURE_URL;
19460                 shim = Ext.get(this.dom.parentNode.insertBefore(el, this.dom));
19461                 shim.autoBoxAdjust = false;
19462                 return shim;
19463             }
19464         };
19465     }()
19466 );
19467 /**
19468  * @class Ext.core.Element
19469  */
19470 Ext.core.Element.addMethods({
19471     /**
19472      * Convenience method for constructing a KeyMap
19473      * @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:
19474      * <code>{key: (number or array), shift: (true/false), ctrl: (true/false), alt: (true/false)}</code>
19475      * @param {Function} fn The function to call
19476      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the specified function is executed. Defaults to this Element.
19477      * @return {Ext.util.KeyMap} The KeyMap created
19478      */
19479     addKeyListener : function(key, fn, scope){
19480         var config;
19481         if(typeof key != 'object' || Ext.isArray(key)){
19482             config = {
19483                 key: key,
19484                 fn: fn,
19485                 scope: scope
19486             };
19487         }else{
19488             config = {
19489                 key : key.key,
19490                 shift : key.shift,
19491                 ctrl : key.ctrl,
19492                 alt : key.alt,
19493                 fn: fn,
19494                 scope: scope
19495             };
19496         }
19497         return Ext.create('Ext.util.KeyMap', this, config);
19498     },
19499
19500     /**
19501      * Creates a KeyMap for this element
19502      * @param {Object} config The KeyMap config. See {@link Ext.util.KeyMap} for more details
19503      * @return {Ext.util.KeyMap} The KeyMap created
19504      */
19505     addKeyMap : function(config){
19506         return Ext.create('Ext.util.KeyMap', this, config);
19507     }
19508 });
19509
19510 //Import the newly-added Ext.core.Element functions into CompositeElementLite. We call this here because
19511 //Element.keys.js is the last extra Ext.core.Element include in the ext-all.js build
19512 Ext.CompositeElementLite.importElementMethods();
19513
19514 /**
19515  * @class Ext.CompositeElementLite
19516  */
19517 Ext.apply(Ext.CompositeElementLite.prototype, {
19518     addElements : function(els, root){
19519         if(!els){
19520             return this;
19521         }
19522         if(typeof els == "string"){
19523             els = Ext.core.Element.selectorFunction(els, root);
19524         }
19525         var yels = this.elements;
19526         Ext.each(els, function(e) {
19527             yels.push(Ext.get(e));
19528         });
19529         return this;
19530     },
19531
19532     /**
19533      * Returns the first Element
19534      * @return {Ext.core.Element}
19535      */
19536     first : function(){
19537         return this.item(0);
19538     },
19539
19540     /**
19541      * Returns the last Element
19542      * @return {Ext.core.Element}
19543      */
19544     last : function(){
19545         return this.item(this.getCount()-1);
19546     },
19547
19548     /**
19549      * Returns true if this composite contains the passed element
19550      * @param el {Mixed} The id of an element, or an Ext.core.Element, or an HtmlElement to find within the composite collection.
19551      * @return Boolean
19552      */
19553     contains : function(el){
19554         return this.indexOf(el) != -1;
19555     },
19556
19557     /**
19558     * Removes the specified element(s).
19559     * @param {Mixed} el The id of an element, the Element itself, the index of the element in this composite
19560     * or an array of any of those.
19561     * @param {Boolean} removeDom (optional) True to also remove the element from the document
19562     * @return {CompositeElement} this
19563     */
19564     removeElement : function(keys, removeDom){
19565         var me = this,
19566             els = this.elements,
19567             el;
19568         Ext.each(keys, function(val){
19569             if ((el = (els[val] || els[val = me.indexOf(val)]))) {
19570                 if(removeDom){
19571                     if(el.dom){
19572                         el.remove();
19573                     }else{
19574                         Ext.removeNode(el);
19575                     }
19576                 }
19577                 els.splice(val, 1);
19578             }
19579         });
19580         return this;
19581     }
19582 });
19583
19584 /**
19585  * @class Ext.CompositeElement
19586  * @extends Ext.CompositeElementLite
19587  * <p>This class encapsulates a <i>collection</i> of DOM elements, providing methods to filter
19588  * members, or to perform collective actions upon the whole set.</p>
19589  * <p>Although they are not listed, this class supports all of the methods of {@link Ext.core.Element} and
19590  * {@link Ext.fx.Anim}. The methods from these classes will be performed on all the elements in this collection.</p>
19591  * <p>All methods return <i>this</i> and can be chained.</p>
19592  * Usage:
19593 <pre><code>
19594 var els = Ext.select("#some-el div.some-class", true);
19595 // or select directly from an existing element
19596 var el = Ext.get('some-el');
19597 el.select('div.some-class', true);
19598
19599 els.setWidth(100); // all elements become 100 width
19600 els.hide(true); // all elements fade out and hide
19601 // or
19602 els.setWidth(100).hide(true);
19603 </code></pre>
19604  */
19605 Ext.CompositeElement = Ext.extend(Ext.CompositeElementLite, {
19606     
19607     constructor : function(els, root){
19608         this.elements = [];
19609         this.add(els, root);
19610     },
19611     
19612     // private
19613     getElement : function(el){
19614         // In this case just return it, since we already have a reference to it
19615         return el;
19616     },
19617     
19618     // private
19619     transformElement : function(el){
19620         return Ext.get(el);
19621     }
19622
19623     /**
19624     * Adds elements to this composite.
19625     * @param {String/Array} els A string CSS selector, an array of elements or an element
19626     * @return {CompositeElement} this
19627     */
19628
19629     /**
19630      * Returns the Element object at the specified index
19631      * @param {Number} index
19632      * @return {Ext.core.Element}
19633      */
19634
19635     /**
19636      * Iterates each `element` in this `composite` calling the supplied function using {@link Ext#each Ext.each}.
19637      * @param {Function} fn 
19638
19639 The function to be called with each
19640 `element`. If the supplied function returns <tt>false</tt>,
19641 iteration stops. This function is called with the following arguments:
19642
19643 - `element` : __Ext.core.Element++
19644     The element at the current `index` in the `composite`
19645     
19646 - `composite` : __Object__ 
19647     This composite.
19648
19649 - `index` : __Number__ 
19650     The current index within the `composite`
19651
19652      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the specified function is executed.
19653      * Defaults to the <code>element</code> at the current <code>index</code>
19654      * within the composite.
19655      * @return {CompositeElement} this
19656      * @markdown
19657      */
19658 });
19659
19660 /**
19661  * Selects elements based on the passed CSS selector to enable {@link Ext.core.Element Element} methods
19662  * to be applied to many related elements in one statement through the returned {@link Ext.CompositeElement CompositeElement} or
19663  * {@link Ext.CompositeElementLite CompositeElementLite} object.
19664  * @param {String/Array} selector The CSS selector or an array of elements
19665  * @param {Boolean} unique (optional) true to create a unique Ext.core.Element for each element (defaults to a shared flyweight object)
19666  * @param {HTMLElement/String} root (optional) The root element of the query or id of the root
19667  * @return {CompositeElementLite/CompositeElement}
19668  * @member Ext.core.Element
19669  * @method select
19670  */
19671 Ext.core.Element.select = function(selector, unique, root){
19672     var els;
19673     if(typeof selector == "string"){
19674         els = Ext.core.Element.selectorFunction(selector, root);
19675     }else if(selector.length !== undefined){
19676         els = selector;
19677     }else{
19678         Ext.Error.raise({
19679             sourceClass: "Ext.core.Element",
19680             sourceMethod: "select",
19681             selector: selector,
19682             unique: unique,
19683             root: root,
19684             msg: "Invalid selector specified: " + selector
19685         });
19686     }
19687     return (unique === true) ? new Ext.CompositeElement(els) : new Ext.CompositeElementLite(els);
19688 };
19689
19690 /**
19691  * Selects elements based on the passed CSS selector to enable {@link Ext.core.Element Element} methods
19692  * to be applied to many related elements in one statement through the returned {@link Ext.CompositeElement CompositeElement} or
19693  * {@link Ext.CompositeElementLite CompositeElementLite} object.
19694  * @param {String/Array} selector The CSS selector or an array of elements
19695  * @param {Boolean} unique (optional) true to create a unique Ext.core.Element for each element (defaults to a shared flyweight object)
19696  * @param {HTMLElement/String} root (optional) The root element of the query or id of the root
19697  * @return {CompositeElementLite/CompositeElement}
19698  * @member Ext
19699  * @method select
19700  */
19701 Ext.select = Ext.core.Element.select;
19702
19703
19704 /*
19705 Ext JS - JavaScript Library
19706 Copyright (c) 2006-2011, Sencha Inc.
19707 All rights reserved.
19708 licensing@sencha.com
19709 */
19710 /**
19711  * @class Ext.util.Observable
19712  * Base class that provides a common interface for publishing events. Subclasses are expected to
19713  * to have a property "events" with all the events defined, and, optionally, a property "listeners"
19714  * with configured listeners defined.<br>
19715  * For example:
19716  * <pre><code>
19717 Employee = Ext.extend(Ext.util.Observable, {
19718     constructor: function(config){
19719         this.name = config.name;
19720         this.addEvents({
19721             "fired" : true,
19722             "quit" : true
19723         });
19724
19725         // Copy configured listeners into *this* object so that the base class&#39;s
19726         // constructor will add them.
19727         this.listeners = config.listeners;
19728
19729         // Call our superclass constructor to complete construction process.
19730         Employee.superclass.constructor.call(this, config)
19731     }
19732 });
19733 </code></pre>
19734  * This could then be used like this:<pre><code>
19735 var newEmployee = new Employee({
19736     name: employeeName,
19737     listeners: {
19738         quit: function() {
19739             // By default, "this" will be the object that fired the event.
19740             alert(this.name + " has quit!");
19741         }
19742     }
19743 });
19744 </code></pre>
19745  */
19746
19747 Ext.define('Ext.util.Observable', {
19748
19749     /* Begin Definitions */
19750
19751     requires: ['Ext.util.Event'],
19752
19753     statics: {
19754         /**
19755          * Removes <b>all</b> added captures from the Observable.
19756          * @param {Observable} o The Observable to release
19757          * @static
19758          */
19759         releaseCapture: function(o) {
19760             o.fireEvent = this.prototype.fireEvent;
19761         },
19762
19763         /**
19764          * Starts capture on the specified Observable. All events will be passed
19765          * to the supplied function with the event name + standard signature of the event
19766          * <b>before</b> the event is fired. If the supplied function returns false,
19767          * the event will not fire.
19768          * @param {Observable} o The Observable to capture events from.
19769          * @param {Function} fn The function to call when an event is fired.
19770          * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the Observable firing the event.
19771          * @static
19772          */
19773         capture: function(o, fn, scope) {
19774             o.fireEvent = Ext.Function.createInterceptor(o.fireEvent, fn, scope);
19775         },
19776
19777         /**
19778 Sets observability on the passed class constructor.
19779
19780 This makes any event fired on any instance of the passed class also fire a single event through
19781 the __class__ allowing for central handling of events on many instances at once.
19782
19783 Usage:
19784
19785     Ext.util.Observable.observe(Ext.data.Connection);
19786     Ext.data.Connection.on('beforerequest', function(con, options) {
19787         console.log('Ajax request made to ' + options.url);
19788     });
19789
19790          * @param {Function} c The class constructor to make observable.
19791          * @param {Object} listeners An object containing a series of listeners to add. See {@link #addListener}.
19792          * @static
19793          * @markdown
19794          */
19795         observe: function(cls, listeners) {
19796             if (cls) {
19797                 if (!cls.isObservable) {
19798                     Ext.applyIf(cls, new this());
19799                     this.capture(cls.prototype, cls.fireEvent, cls);
19800                 }
19801                 if (Ext.isObject(listeners)) {
19802                     cls.on(listeners);
19803                 }
19804                 return cls;
19805             }
19806         }
19807     },
19808
19809     /* End Definitions */
19810
19811     /**
19812     * @cfg {Object} listeners (optional) <p>A config object containing one or more event handlers to be added to this
19813     * object during initialization.  This should be a valid listeners config object as specified in the
19814     * {@link #addListener} example for attaching multiple handlers at once.</p>
19815     * <br><p><b><u>DOM events from ExtJs {@link Ext.Component Components}</u></b></p>
19816     * <br><p>While <i>some</i> ExtJs Component classes export selected DOM events (e.g. "click", "mouseover" etc), this
19817     * is usually only done when extra value can be added. For example the {@link Ext.view.View DataView}'s
19818     * <b><code>{@link Ext.view.View#click click}</code></b> event passing the node clicked on. To access DOM
19819     * events directly from a child element of a Component, we need to specify the <code>element</code> option to
19820     * identify the Component property to add a DOM listener to:
19821     * <pre><code>
19822 new Ext.panel.Panel({
19823     width: 400,
19824     height: 200,
19825     dockedItems: [{
19826         xtype: 'toolbar'
19827     }],
19828     listeners: {
19829         click: {
19830             element: 'el', //bind to the underlying el property on the panel
19831             fn: function(){ console.log('click el'); }
19832         },
19833         dblclick: {
19834             element: 'body', //bind to the underlying body property on the panel
19835             fn: function(){ console.log('dblclick body'); }
19836         }
19837     }
19838 });
19839 </code></pre>
19840     * </p>
19841     */
19842     // @private
19843     isObservable: true,
19844
19845     constructor: function(config) {
19846         var me = this;
19847
19848         Ext.apply(me, config);
19849         if (me.listeners) {
19850             me.on(me.listeners);
19851             delete me.listeners;
19852         }
19853         me.events = me.events || {};
19854
19855         if (me.bubbleEvents) {
19856             me.enableBubble(me.bubbleEvents);
19857         }
19858     },
19859
19860     // @private
19861     eventOptionsRe : /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate|element|vertical|horizontal)$/,
19862
19863     /**
19864      * <p>Adds listeners to any Observable object (or Element) which are automatically removed when this Component
19865      * is destroyed.
19866      * @param {Observable/Element} item The item to which to add a listener/listeners.
19867      * @param {Object/String} ename The event name, or an object containing event name properties.
19868      * @param {Function} fn Optional. If the <code>ename</code> parameter was an event name, this
19869      * is the handler function.
19870      * @param {Object} scope Optional. If the <code>ename</code> parameter was an event name, this
19871      * is the scope (<code>this</code> reference) in which the handler function is executed.
19872      * @param {Object} opt Optional. If the <code>ename</code> parameter was an event name, this
19873      * is the {@link Ext.util.Observable#addListener addListener} options.
19874      */
19875     addManagedListener : function(item, ename, fn, scope, options) {
19876         var me = this,
19877             managedListeners = me.managedListeners = me.managedListeners || [],
19878             config;
19879
19880         if (Ext.isObject(ename)) {
19881             options = ename;
19882             for (ename in options) {
19883                 if (options.hasOwnProperty(ename)) {
19884                     config = options[ename];
19885                     if (!me.eventOptionsRe.test(ename)) {
19886                         me.addManagedListener(item, ename, config.fn || config, config.scope || options.scope, config.fn ? config : options);
19887                     }
19888                 }
19889             }
19890         }
19891         else {
19892             managedListeners.push({
19893                 item: item,
19894                 ename: ename,
19895                 fn: fn,
19896                 scope: scope,
19897                 options: options
19898             });
19899
19900             item.on(ename, fn, scope, options);
19901         }
19902     },
19903
19904     /**
19905      * Removes listeners that were added by the {@link #mon} method.
19906      * @param {Observable|Element} item The item from which to remove a listener/listeners.
19907      * @param {Object|String} ename The event name, or an object containing event name properties.
19908      * @param {Function} fn Optional. If the <code>ename</code> parameter was an event name, this
19909      * is the handler function.
19910      * @param {Object} scope Optional. If the <code>ename</code> parameter was an event name, this
19911      * is the scope (<code>this</code> reference) in which the handler function is executed.
19912      */
19913      removeManagedListener : function(item, ename, fn, scope) {
19914         var me = this,
19915             options,
19916             config,
19917             managedListeners,
19918             managedListener,
19919             length,
19920             i;
19921
19922         if (Ext.isObject(ename)) {
19923             options = ename;
19924             for (ename in options) {
19925                 if (options.hasOwnProperty(ename)) {
19926                     config = options[ename];
19927                     if (!me.eventOptionsRe.test(ename)) {
19928                         me.removeManagedListener(item, ename, config.fn || config, config.scope || options.scope);
19929                     }
19930                 }
19931             }
19932         }
19933
19934         managedListeners = me.managedListeners ? me.managedListeners.slice() : [];
19935         length = managedListeners.length;
19936
19937         for (i = 0; i < length; i++) {
19938             managedListener = managedListeners[i];
19939             if (managedListener.item === item && managedListener.ename === ename && (!fn || managedListener.fn === fn) && (!scope || managedListener.scope === scope)) {
19940                 Ext.Array.remove(me.managedListeners, managedListener);
19941                 item.un(managedListener.ename, managedListener.fn, managedListener.scope);
19942             }
19943         }
19944     },
19945
19946     /**
19947      * <p>Fires the specified event with the passed parameters (minus the event name).</p>
19948      * <p>An event may be set to bubble up an Observable parent hierarchy (See {@link Ext.Component#getBubbleTarget})
19949      * by calling {@link #enableBubble}.</p>
19950      * @param {String} eventName The name of the event to fire.
19951      * @param {Object...} args Variable number of parameters are passed to handlers.
19952      * @return {Boolean} returns false if any of the handlers return false otherwise it returns true.
19953      */
19954     fireEvent: function() {
19955         var me = this,
19956             args = Ext.Array.toArray(arguments),
19957             ename = args[0].toLowerCase(),
19958             ret = true,
19959             event = me.events[ename],
19960             queue = me.eventQueue,
19961             parent;
19962
19963         if (me.eventsSuspended === true) {
19964             if (queue) {
19965                 queue.push(args);
19966             }
19967         } else if (event && Ext.isObject(event) && event.bubble) {
19968             if (event.fire.apply(event, args.slice(1)) === false) {
19969                 return false;
19970             }
19971             parent = me.getBubbleTarget && me.getBubbleTarget();
19972             if (parent && parent.isObservable) {
19973                 if (!parent.events[ename] || !Ext.isObject(parent.events[ename]) || !parent.events[ename].bubble) {
19974                     parent.enableBubble(ename);
19975                 }
19976                 return parent.fireEvent.apply(parent, args);
19977             }
19978         } else if (event && Ext.isObject(event)) {
19979             args.shift();
19980             ret = event.fire.apply(event, args);
19981         }
19982         return ret;
19983     },
19984
19985     /**
19986      * Appends an event handler to this object.
19987      * @param {String}   eventName The name of the event to listen for. May also be an object who's property names are event names. See
19988      * @param {Function} handler The method the event invokes.
19989      * @param {Object}   scope (optional) The scope (<code><b>this</b></code> reference) in which the handler function is executed.
19990      * <b>If omitted, defaults to the object which fired the event.</b>
19991      * @param {Object}   options (optional) An object containing handler configuration.
19992      * properties. This may contain any of the following properties:<ul>
19993      * <li><b>scope</b> : Object<div class="sub-desc">The scope (<code><b>this</b></code> reference) in which the handler function is executed.
19994      * <b>If omitted, defaults to the object which fired the event.</b></div></li>
19995      * <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>
19996      * <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>
19997      * <li><b>buffer</b> : Number<div class="sub-desc">Causes the handler to be scheduled to run in an {@link Ext.util.DelayedTask} delayed
19998      * by the specified number of milliseconds. If the event fires again within that time, the original
19999      * handler is <em>not</em> invoked, but the new handler is scheduled in its place.</div></li>
20000      * <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>
20001      * if the event was bubbled up from a child Observable.</div></li>
20002      * <li><b>element</b> : String<div class="sub-desc"><b>This option is only valid for listeners bound to {@link Ext.Component Components}.</b>
20003      * The name of a Component property which references an element to add a listener to.
20004      * <p>This option is useful during Component construction to add DOM event listeners to elements of {@link Ext.Component Components} which
20005      * will exist only after the Component is rendered. For example, to add a click listener to a Panel's body:<pre><code>
20006 new Ext.panel.Panel({
20007     title: 'The title',
20008     listeners: {
20009         click: this.handlePanelClick,
20010         element: 'body'
20011     }
20012 });
20013 </code></pre></p>
20014      * <p>When added in this way, the options available are the options applicable to {@link Ext.core.Element#addListener}</p></div></li>
20015      * </ul><br>
20016      * <p>
20017      * <b>Combining Options</b><br>
20018      * Using the options argument, it is possible to combine different types of listeners:<br>
20019      * <br>
20020      * A delayed, one-time listener.
20021      * <pre><code>
20022 myPanel.on('hide', this.handleClick, this, {
20023 single: true,
20024 delay: 100
20025 });</code></pre>
20026      * <p>
20027      * <b>Attaching multiple handlers in 1 call</b><br>
20028      * The method also allows for a single argument to be passed which is a config object containing properties
20029      * which specify multiple events. For example:<pre><code>
20030 myGridPanel.on({
20031     cellClick: this.onCellClick,
20032     mouseover: this.onMouseOver,
20033     mouseout: this.onMouseOut,
20034     scope: this // Important. Ensure "this" is correct during handler execution
20035 });
20036 </code></pre>.
20037      * <p>
20038      */
20039     addListener: function(ename, fn, scope, options) {
20040         var me = this,
20041             config,
20042             event;
20043
20044         if (Ext.isObject(ename)) {
20045             options = ename;
20046             for (ename in options) {
20047                 if (options.hasOwnProperty(ename)) {
20048                     config = options[ename];
20049                     if (!me.eventOptionsRe.test(ename)) {
20050                         me.addListener(ename, config.fn || config, config.scope || options.scope, config.fn ? config : options);
20051                     }
20052                 }
20053             }
20054         }
20055         else {
20056             ename = ename.toLowerCase();
20057             me.events[ename] = me.events[ename] || true;
20058             event = me.events[ename] || true;
20059             if (Ext.isBoolean(event)) {
20060                 me.events[ename] = event = new Ext.util.Event(me, ename);
20061             }
20062             event.addListener(fn, scope, Ext.isObject(options) ? options : {});
20063         }
20064     },
20065
20066     /**
20067      * Removes an event handler.
20068      * @param {String}   eventName The type of event the handler was associated with.
20069      * @param {Function} handler   The handler to remove. <b>This must be a reference to the function passed into the {@link #addListener} call.</b>
20070      * @param {Object}   scope     (optional) The scope originally specified for the handler.
20071      */
20072     removeListener: function(ename, fn, scope) {
20073         var me = this,
20074             config,
20075             event,
20076             options;
20077
20078         if (Ext.isObject(ename)) {
20079             options = ename;
20080             for (ename in options) {
20081                 if (options.hasOwnProperty(ename)) {
20082                     config = options[ename];
20083                     if (!me.eventOptionsRe.test(ename)) {
20084                         me.removeListener(ename, config.fn || config, config.scope || options.scope);
20085                     }
20086                 }
20087             }
20088         } else {
20089             ename = ename.toLowerCase();
20090             event = me.events[ename];
20091             if (event.isEvent) {
20092                 event.removeListener(fn, scope);
20093             }
20094         }
20095     },
20096
20097     /**
20098      * Removes all listeners for this object including the managed listeners
20099      */
20100     clearListeners: function() {
20101         var events = this.events,
20102             event,
20103             key;
20104
20105         for (key in events) {
20106             if (events.hasOwnProperty(key)) {
20107                 event = events[key];
20108                 if (event.isEvent) {
20109                     event.clearListeners();
20110                 }
20111             }
20112         }
20113
20114         this.clearManagedListeners();
20115     },
20116
20117     purgeListeners : function() {
20118         console.warn('Observable: purgeListeners has been deprecated. Please use clearListeners.');
20119         return this.clearListeners.apply(this, arguments);
20120     },
20121
20122     /**
20123      * Removes all managed listeners for this object.
20124      */
20125     clearManagedListeners : function() {
20126         var managedListeners = this.managedListeners || [],
20127             i = 0,
20128             len = managedListeners.length,
20129             managedListener;
20130
20131         for (; i < len; i++) {
20132             managedListener = managedListeners[i];
20133             managedListener.item.un(managedListener.ename, managedListener.fn, managedListener.scope);
20134         }
20135
20136         this.managedListeners = [];
20137     },
20138
20139     purgeManagedListeners : function() {
20140         console.warn('Observable: purgeManagedListeners has been deprecated. Please use clearManagedListeners.');
20141         return this.clearManagedListeners.apply(this, arguments);
20142     },
20143
20144     /**
20145      * Adds the specified events to the list of events which this Observable may fire.
20146      * @param {Object/String} o Either an object with event names as properties with a value of <code>true</code>
20147      * or the first event name string if multiple event names are being passed as separate parameters.
20148      * @param {String} [additional] Optional additional event names if multiple event names are being passed as separate parameters.
20149      * Usage:<pre><code>
20150 this.addEvents('storeloaded', 'storecleared');
20151 </code></pre>
20152      */
20153     addEvents: function(o) {
20154         var me = this,
20155             args,
20156             len,
20157             i;
20158             
20159             me.events = me.events || {};
20160         if (Ext.isString(o)) {
20161             args = arguments;
20162             i = args.length;
20163             
20164             while (i--) {
20165                 me.events[args[i]] = me.events[args[i]] || true;
20166             }
20167         } else {
20168             Ext.applyIf(me.events, o);
20169         }
20170     },
20171
20172     /**
20173      * Checks to see if this object has any listeners for a specified event
20174      * @param {String} eventName The name of the event to check for
20175      * @return {Boolean} True if the event is being listened for, else false
20176      */
20177     hasListener: function(ename) {
20178         var event = this.events[ename.toLowerCase()];
20179         return event && event.isEvent === true && event.listeners.length > 0;
20180     },
20181
20182     /**
20183      * Suspend the firing of all events. (see {@link #resumeEvents})
20184      * @param {Boolean} queueSuspended Pass as true to queue up suspended events to be fired
20185      * after the {@link #resumeEvents} call instead of discarding all suspended events;
20186      */
20187     suspendEvents: function(queueSuspended) {
20188         this.eventsSuspended = true;
20189         if (queueSuspended && !this.eventQueue) {
20190             this.eventQueue = [];
20191         }
20192     },
20193
20194     /**
20195      * Resume firing events. (see {@link #suspendEvents})
20196      * If events were suspended using the <code><b>queueSuspended</b></code> parameter, then all
20197      * events fired during event suspension will be sent to any listeners now.
20198      */
20199     resumeEvents: function() {
20200         var me = this,
20201             queued = me.eventQueue || [];
20202
20203         me.eventsSuspended = false;
20204         delete me.eventQueue;
20205
20206         Ext.each(queued,
20207         function(e) {
20208             me.fireEvent.apply(me, e);
20209         });
20210     },
20211
20212     /**
20213      * Relays selected events from the specified Observable as if the events were fired by <code><b>this</b></code>.
20214      * @param {Object} origin The Observable whose events this object is to relay.
20215      * @param {Array} events Array of event names to relay.
20216      */
20217     relayEvents : function(origin, events, prefix) {
20218         prefix = prefix || '';
20219         var me = this,
20220             len = events.length,
20221             i = 0,
20222             oldName,
20223             newName;
20224
20225         for (; i < len; i++) {
20226             oldName = events[i].substr(prefix.length);
20227             newName = prefix + oldName;
20228             me.events[newName] = me.events[newName] || true;
20229             origin.on(oldName, me.createRelayer(newName));
20230         }
20231     },
20232
20233     /**
20234      * @private
20235      * Creates an event handling function which refires the event from this object as the passed event name.
20236      * @param newName
20237      * @returns {Function}
20238      */
20239     createRelayer: function(newName){
20240         var me = this;
20241         return function(){
20242             return me.fireEvent.apply(me, [newName].concat(Array.prototype.slice.call(arguments, 0, -1)));
20243         };
20244     },
20245
20246     /**
20247      * <p>Enables events fired by this Observable to bubble up an owner hierarchy by calling
20248      * <code>this.getBubbleTarget()</code> if present. There is no implementation in the Observable base class.</p>
20249      * <p>This is commonly used by Ext.Components to bubble events to owner Containers. See {@link Ext.Component#getBubbleTarget}. The default
20250      * implementation in Ext.Component returns the Component's immediate owner. But if a known target is required, this can be overridden to
20251      * access the required target more quickly.</p>
20252      * <p>Example:</p><pre><code>
20253 Ext.override(Ext.form.field.Base, {
20254 //  Add functionality to Field&#39;s initComponent to enable the change event to bubble
20255 initComponent : Ext.Function.createSequence(Ext.form.field.Base.prototype.initComponent, function() {
20256     this.enableBubble('change');
20257 }),
20258
20259 //  We know that we want Field&#39;s events to bubble directly to the FormPanel.
20260 getBubbleTarget : function() {
20261     if (!this.formPanel) {
20262         this.formPanel = this.findParentByType('form');
20263     }
20264     return this.formPanel;
20265 }
20266 });
20267
20268 var myForm = new Ext.formPanel({
20269 title: 'User Details',
20270 items: [{
20271     ...
20272 }],
20273 listeners: {
20274     change: function() {
20275         // Title goes red if form has been modified.
20276         myForm.header.setStyle('color', 'red');
20277     }
20278 }
20279 });
20280 </code></pre>
20281      * @param {String/Array} events The event name to bubble, or an Array of event names.
20282      */
20283     enableBubble: function(events) {
20284         var me = this;
20285         if (!Ext.isEmpty(events)) {
20286             events = Ext.isArray(events) ? events: Ext.Array.toArray(arguments);
20287             Ext.each(events,
20288             function(ename) {
20289                 ename = ename.toLowerCase();
20290                 var ce = me.events[ename] || true;
20291                 if (Ext.isBoolean(ce)) {
20292                     ce = new Ext.util.Event(me, ename);
20293                     me.events[ename] = ce;
20294                 }
20295                 ce.bubble = true;
20296             });
20297         }
20298     }
20299 }, function() {
20300     /**
20301      * Removes an event handler (shorthand for {@link #removeListener}.)
20302      * @param {String}   eventName     The type of event the handler was associated with.
20303      * @param {Function} handler       The handler to remove. <b>This must be a reference to the function passed into the {@link #addListener} call.</b>
20304      * @param {Object}   scope         (optional) The scope originally specified for the handler.
20305      * @method un
20306      */
20307
20308     /**
20309      * Appends an event handler to this object (shorthand for {@link #addListener}.)
20310      * @param {String}   eventName     The type of event to listen for
20311      * @param {Function} handler       The method the event invokes
20312      * @param {Object}   scope         (optional) The scope (<code><b>this</b></code> reference) in which the handler function is executed.
20313      * <b>If omitted, defaults to the object which fired the event.</b>
20314      * @param {Object}   options       (optional) An object containing handler configuration.
20315      * @method on
20316      */
20317
20318     this.createAlias({
20319         on: 'addListener',
20320         un: 'removeListener',
20321         mon: 'addManagedListener',
20322         mun: 'removeManagedListener'
20323     });
20324
20325     //deprecated, will be removed in 5.0
20326     this.observeClass = this.observe;
20327
20328     Ext.apply(Ext.util.Observable.prototype, function(){
20329         // this is considered experimental (along with beforeMethod, afterMethod, removeMethodListener?)
20330         // allows for easier interceptor and sequences, including cancelling and overwriting the return value of the call
20331         // private
20332         function getMethodEvent(method){
20333             var e = (this.methodEvents = this.methodEvents || {})[method],
20334                 returnValue,
20335                 v,
20336                 cancel,
20337                 obj = this;
20338
20339             if (!e) {
20340                 this.methodEvents[method] = e = {};
20341                 e.originalFn = this[method];
20342                 e.methodName = method;
20343                 e.before = [];
20344                 e.after = [];
20345
20346                 var makeCall = function(fn, scope, args){
20347                     if((v = fn.apply(scope || obj, args)) !== undefined){
20348                         if (typeof v == 'object') {
20349                             if(v.returnValue !== undefined){
20350                                 returnValue = v.returnValue;
20351                             }else{
20352                                 returnValue = v;
20353                             }
20354                             cancel = !!v.cancel;
20355                         }
20356                         else
20357                             if (v === false) {
20358                                 cancel = true;
20359                             }
20360                             else {
20361                                 returnValue = v;
20362                             }
20363                     }
20364                 };
20365
20366                 this[method] = function(){
20367                     var args = Array.prototype.slice.call(arguments, 0),
20368                         b, i, len;
20369                     returnValue = v = undefined;
20370                     cancel = false;
20371
20372                     for(i = 0, len = e.before.length; i < len; i++){
20373                         b = e.before[i];
20374                         makeCall(b.fn, b.scope, args);
20375                         if (cancel) {
20376                             return returnValue;
20377                         }
20378                     }
20379
20380                     if((v = e.originalFn.apply(obj, args)) !== undefined){
20381                         returnValue = v;
20382                     }
20383
20384                     for(i = 0, len = e.after.length; i < len; i++){
20385                         b = e.after[i];
20386                         makeCall(b.fn, b.scope, args);
20387                         if (cancel) {
20388                             return returnValue;
20389                         }
20390                     }
20391                     return returnValue;
20392                 };
20393             }
20394             return e;
20395         }
20396
20397         return {
20398             // these are considered experimental
20399             // allows for easier interceptor and sequences, including cancelling and overwriting the return value of the call
20400             // adds an 'interceptor' called before the original method
20401             beforeMethod : function(method, fn, scope){
20402                 getMethodEvent.call(this, method).before.push({
20403                     fn: fn,
20404                     scope: scope
20405                 });
20406             },
20407
20408             // adds a 'sequence' called after the original method
20409             afterMethod : function(method, fn, scope){
20410                 getMethodEvent.call(this, method).after.push({
20411                     fn: fn,
20412                     scope: scope
20413                 });
20414             },
20415
20416             removeMethodListener: function(method, fn, scope){
20417                 var e = this.getMethodEvent(method),
20418                     i, len;
20419                 for(i = 0, len = e.before.length; i < len; i++){
20420                     if(e.before[i].fn == fn && e.before[i].scope == scope){
20421                         e.before.splice(i, 1);
20422                         return;
20423                     }
20424                 }
20425                 for(i = 0, len = e.after.length; i < len; i++){
20426                     if(e.after[i].fn == fn && e.after[i].scope == scope){
20427                         e.after.splice(i, 1);
20428                         return;
20429                     }
20430                 }
20431             },
20432
20433             toggleEventLogging: function(toggle) {
20434                 Ext.util.Observable[toggle ? 'capture' : 'releaseCapture'](this, function(en) {
20435                     if (Ext.isDefined(Ext.global.console)) {
20436                         Ext.global.console.log(en, arguments);
20437                     }
20438                 });
20439             }
20440         };
20441     }());
20442 });
20443
20444 /**
20445  * @class Ext.util.Animate
20446  * This animation class is a mixin.
20447  * 
20448  * Ext.util.Animate provides an API for the creation of animated transitions of properties and styles.  
20449  * This class is used as a mixin and currently applied to {@link Ext.core.Element}, {@link Ext.CompositeElement}, 
20450  * {@link Ext.draw.Sprite}, {@link Ext.draw.CompositeSprite}, and {@link Ext.Component}.  Note that Components 
20451  * have a limited subset of what attributes can be animated such as top, left, x, y, height, width, and 
20452  * opacity (color, paddings, and margins can not be animated).
20453  * 
20454  * ## Animation Basics
20455  * 
20456  * All animations require three things - `easing`, `duration`, and `to` (the final end value for each property) 
20457  * you wish to animate. Easing and duration are defaulted values specified below.
20458  * Easing describes how the intermediate values used during a transition will be calculated. 
20459  * {@link Ext.fx.Anim#easing Easing} allows for a transition to change speed over its duration.
20460  * You may use the defaults for easing and duration, but you must always set a 
20461  * {@link Ext.fx.Anim#to to} property which is the end value for all animations.  
20462  * 
20463  * Popular element 'to' configurations are:
20464  * 
20465  *  - opacity
20466  *  - x
20467  *  - y
20468  *  - color
20469  *  - height
20470  *  - width 
20471  * 
20472  * Popular sprite 'to' configurations are:
20473  * 
20474  *  - translation
20475  *  - path
20476  *  - scale
20477  *  - stroke
20478  *  - rotation
20479  * 
20480  * The default duration for animations is 250 (which is a 1/4 of a second).  Duration is denoted in 
20481  * milliseconds.  Therefore 1 second is 1000, 1 minute would be 60000, and so on. The default easing curve 
20482  * used for all animations is 'ease'.  Popular easing functions are included and can be found in {@link Ext.fx.Anim#easing Easing}.
20483  * 
20484  * For example, a simple animation to fade out an element with a default easing and duration:
20485  * 
20486  *     var p1 = Ext.get('myElementId');
20487  * 
20488  *     p1.animate({
20489  *         to: {
20490  *             opacity: 0
20491  *         }
20492  *     });
20493  * 
20494  * To make this animation fade out in a tenth of a second:
20495  * 
20496  *     var p1 = Ext.get('myElementId');
20497  * 
20498  *     p1.animate({
20499  *        duration: 100,
20500  *         to: {
20501  *             opacity: 0
20502  *         }
20503  *     });
20504  * 
20505  * ## Animation Queues
20506  * 
20507  * By default all animations are added to a queue which allows for animation via a chain-style API.
20508  * For example, the following code will queue 4 animations which occur sequentially (one right after the other):
20509  * 
20510  *     p1.animate({
20511  *         to: {
20512  *             x: 500
20513  *         }
20514  *     }).animate({
20515  *         to: {
20516  *             y: 150
20517  *         }
20518  *     }).animate({
20519  *         to: {
20520  *             backgroundColor: '#f00'  //red
20521  *         }
20522  *     }).animate({
20523  *         to: {
20524  *             opacity: 0
20525  *         }
20526  *     });
20527  * 
20528  * You can change this behavior by calling the {@link Ext.util.Animate#syncFx syncFx} method and all 
20529  * subsequent animations for the specified target will be run concurrently (at the same time).
20530  * 
20531  *     p1.syncFx();  //this will make all animations run at the same time
20532  * 
20533  *     p1.animate({
20534  *         to: {
20535  *             x: 500
20536  *         }
20537  *     }).animate({
20538  *         to: {
20539  *             y: 150
20540  *         }
20541  *     }).animate({
20542  *         to: {
20543  *             backgroundColor: '#f00'  //red
20544  *         }
20545  *     }).animate({
20546  *         to: {
20547  *             opacity: 0
20548  *         }
20549  *     });
20550  * 
20551  * This works the same as:
20552  * 
20553  *     p1.animate({
20554  *         to: {
20555  *             x: 500,
20556  *             y: 150,
20557  *             backgroundColor: '#f00'  //red
20558  *             opacity: 0
20559  *         }
20560  *     });
20561  * 
20562  * The {@link Ext.util.Animate#stopAnimation stopAnimation} method can be used to stop any 
20563  * currently running animations and clear any queued animations. 
20564  * 
20565  * ## Animation Keyframes
20566  *
20567  * You can also set up complex animations with {@link Ext.fx.Anim#keyframe keyframe} which follows the 
20568  * CSS3 Animation configuration pattern. Note rotation, translation, and scaling can only be done for sprites. 
20569  * The previous example can be written with the following syntax:
20570  * 
20571  *     p1.animate({
20572  *         duration: 1000,  //one second total
20573  *         keyframes: {
20574  *             25: {     //from 0 to 250ms (25%)
20575  *                 x: 0
20576  *             },
20577  *             50: {   //from 250ms to 500ms (50%)
20578  *                 y: 0
20579  *             },
20580  *             75: {  //from 500ms to 750ms (75%)
20581  *                 backgroundColor: '#f00'  //red
20582  *             },
20583  *             100: {  //from 750ms to 1sec
20584  *                 opacity: 0
20585  *             }
20586  *         }
20587  *     });
20588  * 
20589  * ## Animation Events
20590  * 
20591  * Each animation you create has events for {@link Ext.fx.Anim#beforeanimation beforeanimation}, 
20592  * {@link Ext.fx.Anim#afteranimate afteranimate}, and {@link Ext.fx.Anim#lastframe lastframe}.  
20593  * Keyframed animations adds an additional {@link Ext.fx.Animator#keyframe keyframe} event which 
20594  * fires for each keyframe in your animation.
20595  * 
20596  * All animations support the {@link Ext.util.Observable#listeners listeners} configuration to attact functions to these events.
20597  *    
20598  *     startAnimate: function() {
20599  *         var p1 = Ext.get('myElementId');
20600  *         p1.animate({
20601  *            duration: 100,
20602  *             to: {
20603  *                 opacity: 0
20604  *             },
20605  *             listeners: {
20606  *                 beforeanimate:  function() {
20607  *                     // Execute my custom method before the animation
20608  *                     this.myBeforeAnimateFn();
20609  *                 },
20610  *                 afteranimate: function() {
20611  *                     // Execute my custom method after the animation
20612  *                     this.myAfterAnimateFn();
20613  *                 },
20614  *                 scope: this
20615  *         });
20616  *     },
20617  *     myBeforeAnimateFn: function() {
20618  *       // My custom logic
20619  *     },
20620  *     myAfterAnimateFn: function() {
20621  *       // My custom logic
20622  *     }
20623  * 
20624  * Due to the fact that animations run asynchronously, you can determine if an animation is currently 
20625  * running on any target by using the {@link Ext.util.Animate#getActiveAnimation getActiveAnimation} 
20626  * method.  This method will return false if there are no active animations or return the currently 
20627  * running {@link Ext.fx.Anim} instance.
20628  * 
20629  * In this example, we're going to wait for the current animation to finish, then stop any other 
20630  * queued animations before we fade our element's opacity to 0:
20631  * 
20632  *     var curAnim = p1.getActiveAnimation();
20633  *     if (curAnim) {
20634  *         curAnim.on('afteranimate', function() {
20635  *             p1.stopAnimation();
20636  *             p1.animate({
20637  *                 to: {
20638  *                     opacity: 0
20639  *                 }
20640  *             });
20641  *         });
20642  *     }
20643  * 
20644  * @docauthor Jamie Avins <jamie@sencha.com>
20645  */
20646 Ext.define('Ext.util.Animate', {
20647
20648     uses: ['Ext.fx.Manager', 'Ext.fx.Anim'],
20649
20650     /**
20651      * <p>Perform custom animation on this object.<p>
20652      * <p>This method is applicable to both the the {@link Ext.Component Component} class and the {@link Ext.core.Element Element} class.
20653      * It performs animated transitions of certain properties of this object over a specified timeline.</p>
20654      * <p>The sole parameter is an object which specifies start property values, end property values, and properties which
20655      * describe the timeline. Of the properties listed below, only <b><code>to</code></b> is mandatory.</p>
20656      * <p>Properties include<ul>
20657      * <li><code>from</code> <div class="sub-desc">An object which specifies start values for the properties being animated.
20658      * If not supplied, properties are animated from current settings. The actual properties which may be animated depend upon
20659      * ths object being animated. See the sections below on Element and Component animation.<div></li>
20660      * <li><code>to</code> <div class="sub-desc">An object which specifies end values for the properties being animated.</div></li>
20661      * <li><code>duration</code><div class="sub-desc">The duration <b>in milliseconds</b> for which the animation will run.</div></li>
20662      * <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>
20663      * <li>ease</li>
20664      * <li>easeIn</li>
20665      * <li>easeOut</li>
20666      * <li>easeInOut</li>
20667      * <li>backIn</li>
20668      * <li>backOut</li>
20669      * <li>elasticIn</li>
20670      * <li>elasticOut</li>
20671      * <li>bounceIn</li>
20672      * <li>bounceOut</li>
20673      * </ul></code></div></li>
20674      * <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.
20675      * 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>
20676      * <li><code>listeners</code> <div class="sub-desc">This is a standard {@link Ext.util.Observable#listeners listeners} configuration object which may be used
20677      * to inject behaviour at either the <code>beforeanimate</code> event or the <code>afteranimate</code> event.</div></li>
20678      * </ul></p>
20679      * <h3>Animating an {@link Ext.core.Element Element}</h3>
20680      * When animating an Element, the following properties may be specified in <code>from</code>, <code>to</code>, and <code>keyframe</code> objects:<ul>
20681      * <li><code>x</code> <div class="sub-desc">The page X position in pixels.</div></li>
20682      * <li><code>y</code> <div class="sub-desc">The page Y position in pixels</div></li>
20683      * <li><code>left</code> <div class="sub-desc">The element's CSS <code>left</code> value. Units must be supplied.</div></li>
20684      * <li><code>top</code> <div class="sub-desc">The element's CSS <code>top</code> value. Units must be supplied.</div></li>
20685      * <li><code>width</code> <div class="sub-desc">The element's CSS <code>width</code> value. Units must be supplied.</div></li>
20686      * <li><code>height</code> <div class="sub-desc">The element's CSS <code>height</code> value. Units must be supplied.</div></li>
20687      * <li><code>scrollLeft</code> <div class="sub-desc">The element's <code>scrollLeft</code> value.</div></li>
20688      * <li><code>scrollTop</code> <div class="sub-desc">The element's <code>scrollLeft</code> value.</div></li>
20689      * <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>
20690      * </ul>
20691      * <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
20692      * 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
20693      * directly animate certain properties of Components.</b></p>
20694      * <h3>Animating a {@link Ext.Component Component}</h3>
20695      * When animating an Element, the following properties may be specified in <code>from</code>, <code>to</code>, and <code>keyframe</code> objects:<ul>
20696      * <li><code>x</code> <div class="sub-desc">The Component's page X position in pixels.</div></li>
20697      * <li><code>y</code> <div class="sub-desc">The Component's page Y position in pixels</div></li>
20698      * <li><code>left</code> <div class="sub-desc">The Component's <code>left</code> value in pixels.</div></li>
20699      * <li><code>top</code> <div class="sub-desc">The Component's <code>top</code> value in pixels.</div></li>
20700      * <li><code>width</code> <div class="sub-desc">The Component's <code>width</code> value in pixels.</div></li>
20701      * <li><code>width</code> <div class="sub-desc">The Component's <code>width</code> value in pixels.</div></li>
20702      * <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
20703      * of the animation. <i>Use sparingly as laying out on every intermediate size change is an expensive operation</i>.</div></li>
20704      * </ul>
20705      * <p>For example, to animate a Window to a new size, ensuring that its internal layout, and any shadow is correct:</p>
20706      * <pre><code>
20707 myWindow = Ext.create('Ext.window.Window', {
20708     title: 'Test Component animation',
20709     width: 500,
20710     height: 300,
20711     layout: {
20712         type: 'hbox',
20713         align: 'stretch'
20714     },
20715     items: [{
20716         title: 'Left: 33%',
20717         margins: '5 0 5 5',
20718         flex: 1
20719     }, {
20720         title: 'Left: 66%',
20721         margins: '5 5 5 5',
20722         flex: 2
20723     }]
20724 });
20725 myWindow.show();
20726 myWindow.header.el.on('click', function() {
20727     myWindow.animate({
20728         to: {
20729             width: (myWindow.getWidth() == 500) ? 700 : 500,
20730             height: (myWindow.getHeight() == 300) ? 400 : 300,
20731         }
20732     });
20733 });
20734 </code></pre>
20735      * <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
20736      * 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>
20737      * @param {Object} config An object containing properties which describe the animation's start and end states, and the timeline of the animation.
20738      * @return {Object} this
20739      */
20740     animate: function(animObj) {
20741         var me = this;
20742         if (Ext.fx.Manager.hasFxBlock(me.id)) {
20743             return me;
20744         }
20745         Ext.fx.Manager.queueFx(Ext.create('Ext.fx.Anim', me.anim(animObj)));
20746         return this;
20747     },
20748
20749     // @private - process the passed fx configuration.
20750     anim: function(config) {
20751         if (!Ext.isObject(config)) {
20752             return (config) ? {} : false;
20753         }
20754
20755         var me = this;
20756
20757         if (config.stopAnimation) {
20758             me.stopAnimation();
20759         }
20760
20761         Ext.applyIf(config, Ext.fx.Manager.getFxDefaults(me.id));
20762
20763         return Ext.apply({
20764             target: me,
20765             paused: true
20766         }, config);
20767     },
20768
20769     /**
20770      * Stops any running effects and clears this object's internal effects queue if it contains
20771      * any additional effects that haven't started yet.
20772      * @return {Ext.core.Element} The Element
20773      */
20774     stopFx: Ext.Function.alias(Ext.util.Animate, 'stopAnimation'),
20775
20776     /**
20777      * @deprecated 4.0 Replaced by {@link #stopAnimation}
20778      * Stops any running effects and clears this object's internal effects queue if it contains
20779      * any additional effects that haven't started yet.
20780      * @return {Ext.core.Element} The Element
20781      */
20782     stopAnimation: function() {
20783         Ext.fx.Manager.stopAnimation(this.id);
20784     },
20785
20786     /**
20787      * Ensures that all effects queued after syncFx is called on this object are
20788      * run concurrently.  This is the opposite of {@link #sequenceFx}.
20789      * @return {Ext.core.Element} The Element
20790      */
20791     syncFx: function() {
20792         Ext.fx.Manager.setFxDefaults(this.id, {
20793             concurrent: true
20794         });
20795     },
20796
20797     /**
20798      * Ensures that all effects queued after sequenceFx is called on this object are
20799      * run in sequence.  This is the opposite of {@link #syncFx}.
20800      * @return {Ext.core.Element} The Element
20801      */
20802     sequenceFx: function() {
20803         Ext.fx.Manager.setFxDefaults(this.id, {
20804             concurrent: false
20805         });
20806     },
20807
20808     /**
20809      * @deprecated 4.0 Replaced by {@link #getActiveAnimation}
20810      * Returns thq current animation if this object has any effects actively running or queued, else returns false.
20811      * @return {Mixed} anim if element has active effects, else false
20812      */
20813     hasActiveFx: Ext.Function.alias(Ext.util.Animate, 'getActiveAnimation'),
20814
20815     /**
20816      * Returns thq current animation if this object has any effects actively running or queued, else returns false.
20817      * @return {Mixed} anim if element has active effects, else false
20818      */
20819     getActiveAnimation: function() {
20820         return Ext.fx.Manager.getActiveAnimation(this.id);
20821     }
20822 });
20823
20824 // Apply Animate mixin manually until Element is defined in the proper 4.x way
20825 Ext.applyIf(Ext.core.Element.prototype, Ext.util.Animate.prototype);
20826 /**
20827  * @class Ext.state.Provider
20828  * <p>Abstract base class for state provider implementations. The provider is responsible
20829  * for setting values  and extracting values to/from the underlying storage source. The 
20830  * storage source can vary and the details should be implemented in a subclass. For example
20831  * a provider could use a server side database or the browser localstorage where supported.</p>
20832  *
20833  * <p>This class provides methods for encoding and decoding <b>typed</b> variables including 
20834  * dates and defines the Provider interface. By default these methods put the value and the
20835  * type information into a delimited string that can be stored. These should be overridden in 
20836  * a subclass if you want to change the format of the encoded value and subsequent decoding.</p>
20837  */
20838 Ext.define('Ext.state.Provider', {
20839     mixins: {
20840         observable: 'Ext.util.Observable'
20841     },
20842     
20843     /**
20844      * @cfg {String} prefix A string to prefix to items stored in the underlying state store. 
20845      * Defaults to <tt>'ext-'</tt>
20846      */
20847     prefix: 'ext-',
20848     
20849     constructor : function(config){
20850         config = config || {};
20851         var me = this;
20852         Ext.apply(me, config);
20853         /**
20854          * @event statechange
20855          * Fires when a state change occurs.
20856          * @param {Provider} this This state provider
20857          * @param {String} key The state key which was changed
20858          * @param {String} value The encoded value for the state
20859          */
20860         me.addEvents("statechange");
20861         me.state = {};
20862         me.mixins.observable.constructor.call(me);
20863     },
20864     
20865     /**
20866      * Returns the current value for a key
20867      * @param {String} name The key name
20868      * @param {Mixed} defaultValue A default value to return if the key's value is not found
20869      * @return {Mixed} The state data
20870      */
20871     get : function(name, defaultValue){
20872         return typeof this.state[name] == "undefined" ?
20873             defaultValue : this.state[name];
20874     },
20875
20876     /**
20877      * Clears a value from the state
20878      * @param {String} name The key name
20879      */
20880     clear : function(name){
20881         var me = this;
20882         delete me.state[name];
20883         me.fireEvent("statechange", me, name, null);
20884     },
20885
20886     /**
20887      * Sets the value for a key
20888      * @param {String} name The key name
20889      * @param {Mixed} value The value to set
20890      */
20891     set : function(name, value){
20892         var me = this;
20893         me.state[name] = value;
20894         me.fireEvent("statechange", me, name, value);
20895     },
20896
20897     /**
20898      * Decodes a string previously encoded with {@link #encodeValue}.
20899      * @param {String} value The value to decode
20900      * @return {Mixed} The decoded value
20901      */
20902     decodeValue : function(value){
20903
20904         // a -> Array
20905         // n -> Number
20906         // d -> Date
20907         // b -> Boolean
20908         // s -> String
20909         // o -> Object
20910         // -> Empty (null)
20911
20912         var me = this,
20913             re = /^(a|n|d|b|s|o|e)\:(.*)$/,
20914             matches = re.exec(unescape(value)),
20915             all,
20916             type,
20917             value,
20918             keyValue;
20919             
20920         if(!matches || !matches[1]){
20921             return; // non state
20922         }
20923         
20924         type = matches[1];
20925         value = matches[2];
20926         switch (type) {
20927             case 'e':
20928                 return null;
20929             case 'n':
20930                 return parseFloat(value);
20931             case 'd':
20932                 return new Date(Date.parse(value));
20933             case 'b':
20934                 return (value == '1');
20935             case 'a':
20936                 all = [];
20937                 if(value != ''){
20938                     Ext.each(value.split('^'), function(val){
20939                         all.push(me.decodeValue(val));
20940                     }, me);
20941                 }
20942                 return all;
20943            case 'o':
20944                 all = {};
20945                 if(value != ''){
20946                     Ext.each(value.split('^'), function(val){
20947                         keyValue = val.split('=');
20948                         all[keyValue[0]] = me.decodeValue(keyValue[1]);
20949                     }, me);
20950                 }
20951                 return all;
20952            default:
20953                 return value;
20954         }
20955     },
20956
20957     /**
20958      * Encodes a value including type information.  Decode with {@link #decodeValue}.
20959      * @param {Mixed} value The value to encode
20960      * @return {String} The encoded value
20961      */
20962     encodeValue : function(value){
20963         var flat = '',
20964             i = 0,
20965             enc,
20966             len,
20967             key;
20968             
20969         if (value == null) {
20970             return 'e:1';    
20971         } else if(typeof value == 'number') {
20972             enc = 'n:' + value;
20973         } else if(typeof value == 'boolean') {
20974             enc = 'b:' + (value ? '1' : '0');
20975         } else if(Ext.isDate(value)) {
20976             enc = 'd:' + value.toGMTString();
20977         } else if(Ext.isArray(value)) {
20978             for (len = value.length; i < len; i++) {
20979                 flat += this.encodeValue(value[i]);
20980                 if (i != len - 1) {
20981                     flat += '^';
20982                 }
20983             }
20984             enc = 'a:' + flat;
20985         } else if (typeof value == 'object') {
20986             for (key in value) {
20987                 if (typeof value[key] != 'function' && value[key] !== undefined) {
20988                     flat += key + '=' + this.encodeValue(value[key]) + '^';
20989                 }
20990             }
20991             enc = 'o:' + flat.substring(0, flat.length-1);
20992         } else {
20993             enc = 's:' + value;
20994         }
20995         return escape(enc);
20996     }
20997 });
20998 /**
20999  * @class Ext.util.HashMap
21000  * <p>
21001  * Represents a collection of a set of key and value pairs. Each key in the HashMap
21002  * must be unique, the same key cannot exist twice. Access to items is provided via
21003  * the key only. Sample usage:
21004  * <pre><code>
21005 var map = new Ext.util.HashMap();
21006 map.add('key1', 1);
21007 map.add('key2', 2);
21008 map.add('key3', 3);
21009
21010 map.each(function(key, value, length){
21011     console.log(key, value, length);
21012 });
21013  * </code></pre>
21014  * </p>
21015  *
21016  * <p>The HashMap is an unordered class,
21017  * there is no guarantee when iterating over the items that they will be in any particular
21018  * order. If this is required, then use a {@link Ext.util.MixedCollection}.
21019  * </p>
21020  * @constructor
21021  * @param {Object} config The configuration options
21022  */
21023 Ext.define('Ext.util.HashMap', {
21024
21025     /**
21026      * @cfg {Function} keyFn A function that is used to retrieve a default key for a passed object.
21027      * A default is provided that returns the <b>id</b> property on the object. This function is only used
21028      * if the add method is called with a single argument.
21029      */
21030
21031     mixins: {
21032         observable: 'Ext.util.Observable'
21033     },
21034
21035     constructor: function(config) {
21036         var me = this;
21037
21038         me.addEvents(
21039             /**
21040              * @event add
21041              * Fires when a new item is added to the hash
21042              * @param {Ext.util.HashMap} this.
21043              * @param {String} key The key of the added item.
21044              * @param {Object} value The value of the added item.
21045              */
21046             'add',
21047             /**
21048              * @event clear
21049              * Fires when the hash is cleared.
21050              * @param {Ext.util.HashMap} this.
21051              */
21052             'clear',
21053             /**
21054              * @event remove
21055              * Fires when an item is removed from the hash.
21056              * @param {Ext.util.HashMap} this.
21057              * @param {String} key The key of the removed item.
21058              * @param {Object} value The value of the removed item.
21059              */
21060             'remove',
21061             /**
21062              * @event replace
21063              * Fires when an item is replaced in the hash.
21064              * @param {Ext.util.HashMap} this.
21065              * @param {String} key The key of the replaced item.
21066              * @param {Object} value The new value for the item.
21067              * @param {Object} old The old value for the item.
21068              */
21069             'replace'
21070         );
21071
21072         me.mixins.observable.constructor.call(me, config);
21073         me.clear(true);
21074     },
21075
21076     /**
21077      * Gets the number of items in the hash.
21078      * @return {Number} The number of items in the hash.
21079      */
21080     getCount: function() {
21081         return this.length;
21082     },
21083
21084     /**
21085      * Implementation for being able to extract the key from an object if only
21086      * a single argument is passed.
21087      * @private
21088      * @param {String} key The key
21089      * @param {Object} value The value
21090      * @return {Array} [key, value]
21091      */
21092     getData: function(key, value) {
21093         // if we have no value, it means we need to get the key from the object
21094         if (value === undefined) {
21095             value = key;
21096             key = this.getKey(value);
21097         }
21098
21099         return [key, value];
21100     },
21101
21102     /**
21103      * Extracts the key from an object. This is a default implementation, it may be overridden
21104      * @private
21105      * @param {Object} o The object to get the key from
21106      * @return {String} The key to use.
21107      */
21108     getKey: function(o) {
21109         return o.id;
21110     },
21111
21112     /**
21113      * Adds an item to the collection. Fires the {@link #add} event when complete.
21114      * @param {String} key <p>The key to associate with the item, or the new item.</p>
21115      * <p>If a {@link #getKey} implementation was specified for this HashMap,
21116      * or if the key of the stored items is in a property called <tt><b>id</b></tt>,
21117      * the HashMap will be able to <i>derive</i> the key for the new item.
21118      * In this case just pass the new item in this parameter.</p>
21119      * @param {Object} o The item to add.
21120      * @return {Object} The item added.
21121      */
21122     add: function(key, value) {
21123         var me = this,
21124             data;
21125
21126         if (arguments.length === 1) {
21127             value = key;
21128             key = me.getKey(value);
21129         }
21130
21131         if (me.containsKey(key)) {
21132             me.replace(key, value);
21133         }
21134
21135         data = me.getData(key, value);
21136         key = data[0];
21137         value = data[1];
21138         me.map[key] = value;
21139         ++me.length;
21140         me.fireEvent('add', me, key, value);
21141         return value;
21142     },
21143
21144     /**
21145      * Replaces an item in the hash. If the key doesn't exist, the
21146      * {@link #add} method will be used.
21147      * @param {String} key The key of the item.
21148      * @param {Object} value The new value for the item.
21149      * @return {Object} The new value of the item.
21150      */
21151     replace: function(key, value) {
21152         var me = this,
21153             map = me.map,
21154             old;
21155
21156         if (!me.containsKey(key)) {
21157             me.add(key, value);
21158         }
21159         old = map[key];
21160         map[key] = value;
21161         me.fireEvent('replace', me, key, value, old);
21162         return value;
21163     },
21164
21165     /**
21166      * Remove an item from the hash.
21167      * @param {Object} o The value of the item to remove.
21168      * @return {Boolean} True if the item was successfully removed.
21169      */
21170     remove: function(o) {
21171         var key = this.findKey(o);
21172         if (key !== undefined) {
21173             return this.removeAtKey(key);
21174         }
21175         return false;
21176     },
21177
21178     /**
21179      * Remove an item from the hash.
21180      * @param {String} key The key to remove.
21181      * @return {Boolean} True if the item was successfully removed.
21182      */
21183     removeAtKey: function(key) {
21184         var me = this,
21185             value;
21186
21187         if (me.containsKey(key)) {
21188             value = me.map[key];
21189             delete me.map[key];
21190             --me.length;
21191             me.fireEvent('remove', me, key, value);
21192             return true;
21193         }
21194         return false;
21195     },
21196
21197     /**
21198      * Retrieves an item with a particular key.
21199      * @param {String} key The key to lookup.
21200      * @return {Object} The value at that key. If it doesn't exist, <tt>undefined</tt> is returned.
21201      */
21202     get: function(key) {
21203         return this.map[key];
21204     },
21205
21206     /**
21207      * Removes all items from the hash.
21208      * @return {Ext.util.HashMap} this
21209      */
21210     clear: function(/* private */ initial) {
21211         var me = this;
21212         me.map = {};
21213         me.length = 0;
21214         if (initial !== true) {
21215             me.fireEvent('clear', me);
21216         }
21217         return me;
21218     },
21219
21220     /**
21221      * Checks whether a key exists in the hash.
21222      * @param {String} key The key to check for.
21223      * @return {Boolean} True if they key exists in the hash.
21224      */
21225     containsKey: function(key) {
21226         return this.map[key] !== undefined;
21227     },
21228
21229     /**
21230      * Checks whether a value exists in the hash.
21231      * @param {Object} value The value to check for.
21232      * @return {Boolean} True if the value exists in the dictionary.
21233      */
21234     contains: function(value) {
21235         return this.containsKey(this.findKey(value));
21236     },
21237
21238     /**
21239      * Return all of the keys in the hash.
21240      * @return {Array} An array of keys.
21241      */
21242     getKeys: function() {
21243         return this.getArray(true);
21244     },
21245
21246     /**
21247      * Return all of the values in the hash.
21248      * @return {Array} An array of values.
21249      */
21250     getValues: function() {
21251         return this.getArray(false);
21252     },
21253
21254     /**
21255      * Gets either the keys/values in an array from the hash.
21256      * @private
21257      * @param {Boolean} isKey True to extract the keys, otherwise, the value
21258      * @return {Array} An array of either keys/values from the hash.
21259      */
21260     getArray: function(isKey) {
21261         var arr = [],
21262             key,
21263             map = this.map;
21264         for (key in map) {
21265             if (map.hasOwnProperty(key)) {
21266                 arr.push(isKey ? key: map[key]);
21267             }
21268         }
21269         return arr;
21270     },
21271
21272     /**
21273      * Executes the specified function once for each item in the hash.
21274      * Returning false from the function will cease iteration.
21275      *
21276      * The paramaters passed to the function are:
21277      * <div class="mdetail-params"><ul>
21278      * <li><b>key</b> : String<p class="sub-desc">The key of the item</p></li>
21279      * <li><b>value</b> : Number<p class="sub-desc">The value of the item</p></li>
21280      * <li><b>length</b> : Number<p class="sub-desc">The total number of items in the hash</p></li>
21281      * </ul></div>
21282      * @param {Function} fn The function to execute.
21283      * @param {Object} scope The scope to execute in. Defaults to <tt>this</tt>.
21284      * @return {Ext.util.HashMap} this
21285      */
21286     each: function(fn, scope) {
21287         // copy items so they may be removed during iteration.
21288         var items = Ext.apply({}, this.map),
21289             key,
21290             length = this.length;
21291
21292         scope = scope || this;
21293         for (key in items) {
21294             if (items.hasOwnProperty(key)) {
21295                 if (fn.call(scope, key, items[key], length) === false) {
21296                     break;
21297                 }
21298             }
21299         }
21300         return this;
21301     },
21302
21303     /**
21304      * Performs a shallow copy on this hash.
21305      * @return {Ext.util.HashMap} The new hash object.
21306      */
21307     clone: function() {
21308         var hash = new this.self(),
21309             map = this.map,
21310             key;
21311
21312         hash.suspendEvents();
21313         for (key in map) {
21314             if (map.hasOwnProperty(key)) {
21315                 hash.add(key, map[key]);
21316             }
21317         }
21318         hash.resumeEvents();
21319         return hash;
21320     },
21321
21322     /**
21323      * @private
21324      * Find the key for a value.
21325      * @param {Object} value The value to find.
21326      * @return {Object} The value of the item. Returns <tt>undefined</tt> if not found.
21327      */
21328     findKey: function(value) {
21329         var key,
21330             map = this.map;
21331
21332         for (key in map) {
21333             if (map.hasOwnProperty(key) && map[key] === value) {
21334                 return key;
21335             }
21336         }
21337         return undefined;
21338     }
21339 });
21340
21341 /**
21342  * @class Ext.Template
21343  * <p>Represents an HTML fragment template. Templates may be {@link #compile precompiled}
21344  * for greater performance.</p>
21345  * An instance of this class may be created by passing to the constructor either
21346  * a single argument, or multiple arguments:
21347  * <div class="mdetail-params"><ul>
21348  * <li><b>single argument</b> : String/Array
21349  * <div class="sub-desc">
21350  * The single argument may be either a String or an Array:<ul>
21351  * <li><tt>String</tt> : </li><pre><code>
21352 var t = new Ext.Template("&lt;div>Hello {0}.&lt;/div>");
21353 t.{@link #append}('some-element', ['foo']);
21354    </code></pre>
21355  * <li><tt>Array</tt> : </li>
21356  * An Array will be combined with <code>join('')</code>.
21357 <pre><code>
21358 var t = new Ext.Template([
21359     '&lt;div name="{id}"&gt;',
21360         '&lt;span class="{cls}"&gt;{name:trim} {value:ellipsis(10)}&lt;/span&gt;',
21361     '&lt;/div&gt;',
21362 ]);
21363 t.{@link #compile}();
21364 t.{@link #append}('some-element', {id: 'myid', cls: 'myclass', name: 'foo', value: 'bar'});
21365    </code></pre>
21366  * </ul></div></li>
21367  * <li><b>multiple arguments</b> : String, Object, Array, ...
21368  * <div class="sub-desc">
21369  * Multiple arguments will be combined with <code>join('')</code>.
21370  * <pre><code>
21371 var t = new Ext.Template(
21372     '&lt;div name="{id}"&gt;',
21373         '&lt;span class="{cls}"&gt;{name} {value}&lt;/span&gt;',
21374     '&lt;/div&gt;',
21375     // a configuration object:
21376     {
21377         compiled: true,      // {@link #compile} immediately
21378     }
21379 );
21380    </code></pre>
21381  * <p><b>Notes</b>:</p>
21382  * <div class="mdetail-params"><ul>
21383  * <li>For a list of available format functions, see {@link Ext.util.Format}.</li>
21384  * <li><code>disableFormats</code> reduces <code>{@link #apply}</code> time
21385  * when no formatting is required.</li>
21386  * </ul></div>
21387  * </div></li>
21388  * </ul></div>
21389  * @param {Mixed} config
21390  */
21391
21392 Ext.define('Ext.Template', {
21393
21394     /* Begin Definitions */
21395
21396     requires: ['Ext.core.DomHelper', 'Ext.util.Format'],
21397
21398     statics: {
21399         /**
21400          * Creates a template from the passed element's value (<i>display:none</i> textarea, preferred) or innerHTML.
21401          * @param {String/HTMLElement} el A DOM element or its id
21402          * @param {Object} config A configuration object
21403          * @return {Ext.Template} The created template
21404          * @static
21405          */
21406         from: function(el, config) {
21407             el = Ext.getDom(el);
21408             return new this(el.value || el.innerHTML, config || '');
21409         }
21410     },
21411
21412     /* End Definitions */
21413
21414     constructor: function(html) {
21415         var me = this,
21416             args = arguments,
21417             buffer = [],
21418             i = 0,
21419             length = args.length,
21420             value;
21421
21422         me.initialConfig = {};
21423
21424         if (length > 1) {
21425             for (; i < length; i++) {
21426                 value = args[i];
21427                 if (typeof value == 'object') {
21428                     Ext.apply(me.initialConfig, value);
21429                     Ext.apply(me, value);
21430                 } else {
21431                     buffer.push(value);
21432                 }
21433             }
21434             html = buffer.join('');
21435         } else {
21436             if (Ext.isArray(html)) {
21437                 buffer.push(html.join(''));
21438             } else {
21439                 buffer.push(html);
21440             }
21441         }
21442
21443         // @private
21444         me.html = buffer.join('');
21445
21446         if (me.compiled) {
21447             me.compile();
21448         }
21449     },
21450     isTemplate: true,
21451     /**
21452      * @cfg {Boolean} disableFormats true to disable format functions in the template. If the template doesn't contain format functions, setting
21453      * disableFormats to true will reduce apply time (defaults to false)
21454      */
21455     disableFormats: false,
21456
21457     re: /\{([\w\-]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?\}/g,
21458     /**
21459      * Returns an HTML fragment of this template with the specified values applied.
21460      * @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'})
21461      * @return {String} The HTML fragment
21462      * @hide repeat doc
21463      */
21464     applyTemplate: function(values) {
21465         var me = this,
21466             useFormat = me.disableFormats !== true,
21467             fm = Ext.util.Format,
21468             tpl = me;
21469
21470         if (me.compiled) {
21471             return me.compiled(values);
21472         }
21473         function fn(m, name, format, args) {
21474             if (format && useFormat) {
21475                 if (args) {
21476                     args = [values[name]].concat(Ext.functionFactory('return ['+ args +'];')());
21477                 } else {
21478                     args = [values[name]];
21479                 }
21480                 if (format.substr(0, 5) == "this.") {
21481                     return tpl[format.substr(5)].apply(tpl, args);
21482                 }
21483                 else {
21484                     return fm[format].apply(fm, args);
21485                 }
21486             }
21487             else {
21488                 return values[name] !== undefined ? values[name] : "";
21489             }
21490         }
21491         return me.html.replace(me.re, fn);
21492     },
21493
21494     /**
21495      * Sets the HTML used as the template and optionally compiles it.
21496      * @param {String} html
21497      * @param {Boolean} compile (optional) True to compile the template (defaults to undefined)
21498      * @return {Ext.Template} this
21499      */
21500     set: function(html, compile) {
21501         var me = this;
21502         me.html = html;
21503         me.compiled = null;
21504         return compile ? me.compile() : me;
21505     },
21506
21507     compileARe: /\\/g,
21508     compileBRe: /(\r\n|\n)/g,
21509     compileCRe: /'/g,
21510     /**
21511      * Compiles the template into an internal function, eliminating the RegEx overhead.
21512      * @return {Ext.Template} this
21513      * @hide repeat doc
21514      */
21515     compile: function() {
21516         var me = this,
21517             fm = Ext.util.Format,
21518             useFormat = me.disableFormats !== true,
21519             body, bodyReturn;
21520
21521         function fn(m, name, format, args) {
21522             if (format && useFormat) {
21523                 args = args ? ',' + args: "";
21524                 if (format.substr(0, 5) != "this.") {
21525                     format = "fm." + format + '(';
21526                 }
21527                 else {
21528                     format = 'this.' + format.substr(5) + '(';
21529                 }
21530             }
21531             else {
21532                 args = '';
21533                 format = "(values['" + name + "'] == undefined ? '' : ";
21534             }
21535             return "'," + format + "values['" + name + "']" + args + ") ,'";
21536         }
21537
21538         bodyReturn = me.html.replace(me.compileARe, '\\\\').replace(me.compileBRe, '\\n').replace(me.compileCRe, "\\'").replace(me.re, fn);
21539         body = "this.compiled = function(values){ return ['" + bodyReturn + "'].join('');};";
21540         eval(body);
21541         return me;
21542     },
21543
21544     /**
21545      * Applies the supplied values to the template and inserts the new node(s) as the first child of el.
21546      * @param {Mixed} el The context element
21547      * @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'})
21548      * @param {Boolean} returnElement (optional) true to return a Ext.core.Element (defaults to undefined)
21549      * @return {HTMLElement/Ext.core.Element} The new node or Element
21550      */
21551     insertFirst: function(el, values, returnElement) {
21552         return this.doInsert('afterBegin', el, values, returnElement);
21553     },
21554
21555     /**
21556      * Applies the supplied values to the template and inserts the new node(s) before el.
21557      * @param {Mixed} el The context element
21558      * @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'})
21559      * @param {Boolean} returnElement (optional) true to return a Ext.core.Element (defaults to undefined)
21560      * @return {HTMLElement/Ext.core.Element} The new node or Element
21561      */
21562     insertBefore: function(el, values, returnElement) {
21563         return this.doInsert('beforeBegin', el, values, returnElement);
21564     },
21565
21566     /**
21567      * Applies the supplied values to the template and inserts the new node(s) after el.
21568      * @param {Mixed} el The context element
21569      * @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'})
21570      * @param {Boolean} returnElement (optional) true to return a Ext.core.Element (defaults to undefined)
21571      * @return {HTMLElement/Ext.core.Element} The new node or Element
21572      */
21573     insertAfter: function(el, values, returnElement) {
21574         return this.doInsert('afterEnd', el, values, returnElement);
21575     },
21576
21577     /**
21578      * Applies the supplied <code>values</code> to the template and appends
21579      * the new node(s) to the specified <code>el</code>.
21580      * <p>For example usage {@link #Template see the constructor}.</p>
21581      * @param {Mixed} el The context element
21582      * @param {Object/Array} values
21583      * The template values. Can be an array if the params are numeric (i.e. <code>{0}</code>)
21584      * or an object (i.e. <code>{foo: 'bar'}</code>).
21585      * @param {Boolean} returnElement (optional) true to return an Ext.core.Element (defaults to undefined)
21586      * @return {HTMLElement/Ext.core.Element} The new node or Element
21587      */
21588     append: function(el, values, returnElement) {
21589         return this.doInsert('beforeEnd', el, values, returnElement);
21590     },
21591
21592     doInsert: function(where, el, values, returnEl) {
21593         el = Ext.getDom(el);
21594         var newNode = Ext.core.DomHelper.insertHtml(where, el, this.applyTemplate(values));
21595         return returnEl ? Ext.get(newNode, true) : newNode;
21596     },
21597
21598     /**
21599      * Applies the supplied values to the template and overwrites the content of el with the new node(s).
21600      * @param {Mixed} el The context element
21601      * @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'})
21602      * @param {Boolean} returnElement (optional) true to return a Ext.core.Element (defaults to undefined)
21603      * @return {HTMLElement/Ext.core.Element} The new node or Element
21604      */
21605     overwrite: function(el, values, returnElement) {
21606         el = Ext.getDom(el);
21607         el.innerHTML = this.applyTemplate(values);
21608         return returnElement ? Ext.get(el.firstChild, true) : el.firstChild;
21609     }
21610 }, function() {
21611
21612     /**
21613      * Alias for {@link #applyTemplate}
21614      * Returns an HTML fragment of this template with the specified <code>values</code> applied.
21615      * @param {Object/Array} values
21616      * The template values. Can be an array if the params are numeric (i.e. <code>{0}</code>)
21617      * or an object (i.e. <code>{foo: 'bar'}</code>).
21618      * @return {String} The HTML fragment
21619      * @member Ext.Template
21620      * @method apply
21621      */
21622     this.createAlias('apply', 'applyTemplate');
21623 });
21624
21625 /**
21626  * @class Ext.ComponentQuery
21627  * @extends Object
21628  *
21629  * Provides searching of Components within Ext.ComponentManager (globally) or a specific
21630  * Ext.container.Container on the document with a similar syntax to a CSS selector.
21631  *
21632  * Components can be retrieved by using their {@link Ext.Component xtype} with an optional . prefix
21633 <ul>
21634     <li>component or .component</li>
21635     <li>gridpanel or .gridpanel</li>
21636 </ul>
21637  *
21638  * An itemId or id must be prefixed with a #
21639 <ul>
21640     <li>#myContainer</li>
21641 </ul>
21642  *
21643  *
21644  * Attributes must be wrapped in brackets
21645 <ul>
21646     <li>component[autoScroll]</li>
21647     <li>panel[title="Test"]</li>
21648 </ul>
21649  *
21650  * Member expressions from candidate Components may be tested. If the expression returns a <i>truthy</i> value,
21651  * the candidate Component will be included in the query:<pre><code>
21652 var disabledFields = myFormPanel.query("{isDisabled()}");
21653 </code></pre>
21654  *
21655  * Pseudo classes may be used to filter results in the same way as in {@link Ext.DomQuery DomQuery}:<code><pre>
21656 // Function receives array and returns a filtered array.
21657 Ext.ComponentQuery.pseudos.invalid = function(items) {
21658     var i = 0, l = items.length, c, result = [];
21659     for (; i < l; i++) {
21660         if (!(c = items[i]).isValid()) {
21661             result.push(c);
21662         }
21663     }
21664     return result;
21665 };
21666
21667 var invalidFields = myFormPanel.query('field:invalid');
21668 if (invalidFields.length) {
21669     invalidFields[0].getEl().scrollIntoView(myFormPanel.body);
21670     for (var i = 0, l = invalidFields.length; i < l; i++) {
21671         invalidFields[i].getEl().frame("red");
21672     }
21673 }
21674 </pre></code>
21675  * <p>
21676  * Default pseudos include:<br />
21677  * - not
21678  * </p>
21679  *
21680  * Queries return an array of components.
21681  * Here are some example queries.
21682 <pre><code>
21683     // retrieve all Ext.Panels in the document by xtype
21684     var panelsArray = Ext.ComponentQuery.query('panel');
21685
21686     // retrieve all Ext.Panels within the container with an id myCt
21687     var panelsWithinmyCt = Ext.ComponentQuery.query('#myCt panel');
21688
21689     // retrieve all direct children which are Ext.Panels within myCt
21690     var directChildPanel = Ext.ComponentQuery.query('#myCt > panel');
21691
21692     // retrieve all gridpanels and listviews
21693     var gridsAndLists = Ext.ComponentQuery.query('gridpanel, listview');
21694 </code></pre>
21695
21696 For easy access to queries based from a particular Container see the {@link Ext.container.Container#query},
21697 {@link Ext.container.Container#down} and {@link Ext.container.Container#child} methods. Also see
21698 {@link Ext.Component#up}.
21699  * @singleton
21700  */
21701 Ext.define('Ext.ComponentQuery', {
21702     singleton: true,
21703     uses: ['Ext.ComponentManager']
21704 }, function() {
21705
21706     var cq = this,
21707
21708         // A function source code pattern with a placeholder which accepts an expression which yields a truth value when applied
21709         // as a member on each item in the passed array.
21710         filterFnPattern = [
21711             'var r = [],',
21712                 'i = 0,',
21713                 'it = items,',
21714                 'l = it.length,',
21715                 'c;',
21716             'for (; i < l; i++) {',
21717                 'c = it[i];',
21718                 'if (c.{0}) {',
21719                    'r.push(c);',
21720                 '}',
21721             '}',
21722             'return r;'
21723         ].join(''),
21724
21725         filterItems = function(items, operation) {
21726             // Argument list for the operation is [ itemsArray, operationArg1, operationArg2...]
21727             // The operation's method loops over each item in the candidate array and
21728             // returns an array of items which match its criteria
21729             return operation.method.apply(this, [ items ].concat(operation.args));
21730         },
21731
21732         getItems = function(items, mode) {
21733             var result = [],
21734                 i = 0,
21735                 length = items.length,
21736                 candidate,
21737                 deep = mode !== '>';
21738                 
21739             for (; i < length; i++) {
21740                 candidate = items[i];
21741                 if (candidate.getRefItems) {
21742                     result = result.concat(candidate.getRefItems(deep));
21743                 }
21744             }
21745             return result;
21746         },
21747
21748         getAncestors = function(items) {
21749             var result = [],
21750                 i = 0,
21751                 length = items.length,
21752                 candidate;
21753             for (; i < length; i++) {
21754                 candidate = items[i];
21755                 while (!!(candidate = (candidate.ownerCt || candidate.floatParent))) {
21756                     result.push(candidate);
21757                 }
21758             }
21759             return result;
21760         },
21761
21762         // Filters the passed candidate array and returns only items which match the passed xtype
21763         filterByXType = function(items, xtype, shallow) {
21764             if (xtype === '*') {
21765                 return items.slice();
21766             }
21767             else {
21768                 var result = [],
21769                     i = 0,
21770                     length = items.length,
21771                     candidate;
21772                 for (; i < length; i++) {
21773                     candidate = items[i];
21774                     if (candidate.isXType(xtype, shallow)) {
21775                         result.push(candidate);
21776                     }
21777                 }
21778                 return result;
21779             }
21780         },
21781
21782         // Filters the passed candidate array and returns only items which have the passed className
21783         filterByClassName = function(items, className) {
21784             var EA = Ext.Array,
21785                 result = [],
21786                 i = 0,
21787                 length = items.length,
21788                 candidate;
21789             for (; i < length; i++) {
21790                 candidate = items[i];
21791                 if (candidate.el ? candidate.el.hasCls(className) : EA.contains(candidate.initCls(), className)) {
21792                     result.push(candidate);
21793                 }
21794             }
21795             return result;
21796         },
21797
21798         // Filters the passed candidate array and returns only items which have the specified property match
21799         filterByAttribute = function(items, property, operator, value) {
21800             var result = [],
21801                 i = 0,
21802                 length = items.length,
21803                 candidate;
21804             for (; i < length; i++) {
21805                 candidate = items[i];
21806                 if (!value ? !!candidate[property] : (String(candidate[property]) === value)) {
21807                     result.push(candidate);
21808                 }
21809             }
21810             return result;
21811         },
21812
21813         // Filters the passed candidate array and returns only items which have the specified itemId or id
21814         filterById = function(items, id) {
21815             var result = [],
21816                 i = 0,
21817                 length = items.length,
21818                 candidate;
21819             for (; i < length; i++) {
21820                 candidate = items[i];
21821                 if (candidate.getItemId() === id) {
21822                     result.push(candidate);
21823                 }
21824             }
21825             return result;
21826         },
21827
21828         // Filters the passed candidate array and returns only items which the named pseudo class matcher filters in
21829         filterByPseudo = function(items, name, value) {
21830             return cq.pseudos[name](items, value);
21831         },
21832
21833         // Determines leading mode
21834         // > for direct child, and ^ to switch to ownerCt axis
21835         modeRe = /^(\s?([>\^])\s?|\s|$)/,
21836
21837         // Matches a token with possibly (true|false) appended for the "shallow" parameter
21838         tokenRe = /^(#)?([\w\-]+|\*)(?:\((true|false)\))?/,
21839
21840         matchers = [{
21841             // Checks for .xtype with possibly (true|false) appended for the "shallow" parameter
21842             re: /^\.([\w\-]+)(?:\((true|false)\))?/,
21843             method: filterByXType
21844         },{
21845             // checks for [attribute=value]
21846             re: /^(?:[\[](?:@)?([\w\-]+)\s?(?:(=|.=)\s?['"]?(.*?)["']?)?[\]])/,
21847             method: filterByAttribute
21848         }, {
21849             // checks for #cmpItemId
21850             re: /^#([\w\-]+)/,
21851             method: filterById
21852         }, {
21853             // checks for :<pseudo_class>(<selector>)
21854             re: /^\:([\w\-]+)(?:\(((?:\{[^\}]+\})|(?:(?!\{)[^\s>\/]*?(?!\})))\))?/,
21855             method: filterByPseudo
21856         }, {
21857             // checks for {<member_expression>}
21858             re: /^(?:\{([^\}]+)\})/,
21859             method: filterFnPattern
21860         }];
21861
21862     /**
21863      * @class Ext.ComponentQuery.Query
21864      * @extends Object
21865      * @private
21866      */
21867     cq.Query = Ext.extend(Object, {
21868         constructor: function(cfg) {
21869             cfg = cfg || {};
21870             Ext.apply(this, cfg);
21871         },
21872
21873         /**
21874          * @private
21875          * Executes this Query upon the selected root.
21876          * The root provides the initial source of candidate Component matches which are progressively
21877          * filtered by iterating through this Query's operations cache.
21878          * If no root is provided, all registered Components are searched via the ComponentManager.
21879          * root may be a Container who's descendant Components are filtered
21880          * root may be a Component with an implementation of getRefItems which provides some nested Components such as the
21881          * docked items within a Panel.
21882          * root may be an array of candidate Components to filter using this Query.
21883          */
21884         execute : function(root) {
21885             var operations = this.operations,
21886                 i = 0,
21887                 length = operations.length,
21888                 operation,
21889                 workingItems;
21890
21891             // no root, use all Components in the document
21892             if (!root) {
21893                 workingItems = Ext.ComponentManager.all.getArray();
21894             }
21895             // Root is a candidate Array
21896             else if (Ext.isArray(root)) {
21897                 workingItems = root;
21898             }
21899
21900             // We are going to loop over our operations and take care of them
21901             // one by one.
21902             for (; i < length; i++) {
21903                 operation = operations[i];
21904
21905                 // The mode operation requires some custom handling.
21906                 // All other operations essentially filter down our current
21907                 // working items, while mode replaces our current working
21908                 // items by getting children from each one of our current
21909                 // working items. The type of mode determines the type of
21910                 // children we get. (e.g. > only gets direct children)
21911                 if (operation.mode === '^') {
21912                     workingItems = getAncestors(workingItems || [root]);
21913                 }
21914                 else if (operation.mode) {
21915                     workingItems = getItems(workingItems || [root], operation.mode);
21916                 }
21917                 else {
21918                     workingItems = filterItems(workingItems || getItems([root]), operation);
21919                 }
21920
21921                 // If this is the last operation, it means our current working
21922                 // items are the final matched items. Thus return them!
21923                 if (i === length -1) {
21924                     return workingItems;
21925                 }
21926             }
21927             return [];
21928         },
21929
21930         is: function(component) {
21931             var operations = this.operations,
21932                 components = Ext.isArray(component) ? component : [component],
21933                 originalLength = components.length,
21934                 lastOperation = operations[operations.length-1],
21935                 ln, i;
21936
21937             components = filterItems(components, lastOperation);
21938             if (components.length === originalLength) {
21939                 if (operations.length > 1) {
21940                     for (i = 0, ln = components.length; i < ln; i++) {
21941                         if (Ext.Array.indexOf(this.execute(), components[i]) === -1) {
21942                             return false;
21943                         }
21944                     }
21945                 }
21946                 return true;
21947             }
21948             return false;
21949         }
21950     });
21951
21952     Ext.apply(this, {
21953
21954         // private cache of selectors and matching ComponentQuery.Query objects
21955         cache: {},
21956
21957         // private cache of pseudo class filter functions
21958         pseudos: {
21959             not: function(components, selector){
21960                 var CQ = Ext.ComponentQuery,
21961                     i = 0,
21962                     length = components.length,
21963                     results = [],
21964                     index = -1,
21965                     component;
21966                 
21967                 for(; i < length; ++i) {
21968                     component = components[i];
21969                     if (!CQ.is(component, selector)) {
21970                         results[++index] = component;
21971                     }
21972                 }
21973                 return results;
21974             }
21975         },
21976
21977         /**
21978          * <p>Returns an array of matched Components from within the passed root object.</p>
21979          * <p>This method filters returned Components in a similar way to how CSS selector based DOM
21980          * queries work using a textual selector string.</p>
21981          * <p>See class summary for details.</p>
21982          * @param selector The selector string to filter returned Components
21983          * @param root <p>The Container within which to perform the query. If omitted, all Components
21984          * within the document are included in the search.</p>
21985          * <p>This parameter may also be an array of Components to filter according to the selector.</p>
21986          * @returns {Array} The matched Components.
21987          * @member Ext.ComponentQuery
21988          * @method query
21989          */
21990         query: function(selector, root) {
21991             var selectors = selector.split(','),
21992                 length = selectors.length,
21993                 i = 0,
21994                 results = [],
21995                 noDupResults = [], 
21996                 dupMatcher = {}, 
21997                 query, resultsLn, cmp;
21998
21999             for (; i < length; i++) {
22000                 selector = Ext.String.trim(selectors[i]);
22001                 query = this.cache[selector];
22002                 if (!query) {
22003                     this.cache[selector] = query = this.parse(selector);
22004                 }
22005                 results = results.concat(query.execute(root));
22006             }
22007
22008             // multiple selectors, potential to find duplicates
22009             // lets filter them out.
22010             if (length > 1) {
22011                 resultsLn = results.length;
22012                 for (i = 0; i < resultsLn; i++) {
22013                     cmp = results[i];
22014                     if (!dupMatcher[cmp.id]) {
22015                         noDupResults.push(cmp);
22016                         dupMatcher[cmp.id] = true;
22017                     }
22018                 }
22019                 results = noDupResults;
22020             }
22021             return results;
22022         },
22023
22024         /**
22025          * Tests whether the passed Component matches the selector string.
22026          * @param component The Component to test
22027          * @param selector The selector string to test against.
22028          * @return {Boolean} True if the Component matches the selector.
22029          * @member Ext.ComponentQuery
22030          * @method query
22031          */
22032         is: function(component, selector) {
22033             if (!selector) {
22034                 return true;
22035             }
22036             var query = this.cache[selector];
22037             if (!query) {
22038                 this.cache[selector] = query = this.parse(selector);
22039             }
22040             return query.is(component);
22041         },
22042
22043         parse: function(selector) {
22044             var operations = [],
22045                 length = matchers.length,
22046                 lastSelector,
22047                 tokenMatch,
22048                 matchedChar,
22049                 modeMatch,
22050                 selectorMatch,
22051                 i, matcher, method;
22052
22053             // We are going to parse the beginning of the selector over and
22054             // over again, slicing off the selector any portions we converted into an
22055             // operation, until it is an empty string.
22056             while (selector && lastSelector !== selector) {
22057                 lastSelector = selector;
22058
22059                 // First we check if we are dealing with a token like #, * or an xtype
22060                 tokenMatch = selector.match(tokenRe);
22061
22062                 if (tokenMatch) {
22063                     matchedChar = tokenMatch[1];
22064
22065                     // If the token is prefixed with a # we push a filterById operation to our stack
22066                     if (matchedChar === '#') {
22067                         operations.push({
22068                             method: filterById,
22069                             args: [Ext.String.trim(tokenMatch[2])]
22070                         });
22071                     }
22072                     // If the token is prefixed with a . we push a filterByClassName operation to our stack
22073                     // FIXME: Not enabled yet. just needs \. adding to the tokenRe prefix
22074                     else if (matchedChar === '.') {
22075                         operations.push({
22076                             method: filterByClassName,
22077                             args: [Ext.String.trim(tokenMatch[2])]
22078                         });
22079                     }
22080                     // If the token is a * or an xtype string, we push a filterByXType
22081                     // operation to the stack.
22082                     else {
22083                         operations.push({
22084                             method: filterByXType,
22085                             args: [Ext.String.trim(tokenMatch[2]), Boolean(tokenMatch[3])]
22086                         });
22087                     }
22088
22089                     // Now we slice of the part we just converted into an operation
22090                     selector = selector.replace(tokenMatch[0], '');
22091                 }
22092
22093                 // If the next part of the query is not a space or > or ^, it means we
22094                 // are going to check for more things that our current selection
22095                 // has to comply to.
22096                 while (!(modeMatch = selector.match(modeRe))) {
22097                     // Lets loop over each type of matcher and execute it
22098                     // on our current selector.
22099                     for (i = 0; selector && i < length; i++) {
22100                         matcher = matchers[i];
22101                         selectorMatch = selector.match(matcher.re);
22102                         method = matcher.method;
22103
22104                         // If we have a match, add an operation with the method
22105                         // associated with this matcher, and pass the regular
22106                         // expression matches are arguments to the operation.
22107                         if (selectorMatch) {
22108                             operations.push({
22109                                 method: Ext.isString(matcher.method)
22110                                     // Turn a string method into a function by formatting the string with our selector matche expression
22111                                     // A new method is created for different match expressions, eg {id=='textfield-1024'}
22112                                     // Every expression may be different in different selectors.
22113                                     ? Ext.functionFactory('items', Ext.String.format.apply(Ext.String, [method].concat(selectorMatch.slice(1))))
22114                                     : matcher.method,
22115                                 args: selectorMatch.slice(1)
22116                             });
22117                             selector = selector.replace(selectorMatch[0], '');
22118                             break; // Break on match
22119                         }
22120                         // Exhausted all matches: It's an error
22121                         if (i === (length - 1)) {
22122                             Ext.Error.raise('Invalid ComponentQuery selector: "' + arguments[0] + '"');
22123                         }
22124                     }
22125                 }
22126
22127                 // Now we are going to check for a mode change. This means a space
22128                 // or a > to determine if we are going to select all the children
22129                 // of the currently matched items, or a ^ if we are going to use the
22130                 // ownerCt axis as the candidate source.
22131                 if (modeMatch[1]) { // Assignment, and test for truthiness!
22132                     operations.push({
22133                         mode: modeMatch[2]||modeMatch[1]
22134                     });
22135                     selector = selector.replace(modeMatch[0], '');
22136                 }
22137             }
22138
22139             //  Now that we have all our operations in an array, we are going
22140             // to create a new Query using these operations.
22141             return new cq.Query({
22142                 operations: operations
22143             });
22144         }
22145     });
22146 });
22147 /**
22148  * @class Ext.util.Filter
22149  * @extends Object
22150  * <p>Represents a filter that can be applied to a {@link Ext.util.MixedCollection MixedCollection}. Can either simply
22151  * filter on a property/value pair or pass in a filter function with custom logic. Filters are always used in the context
22152  * of MixedCollections, though {@link Ext.data.Store Store}s frequently create them when filtering and searching on their
22153  * records. Example usage:</p>
22154 <pre><code>
22155 //set up a fictional MixedCollection containing a few people to filter on
22156 var allNames = new Ext.util.MixedCollection();
22157 allNames.addAll([
22158     {id: 1, name: 'Ed',    age: 25},
22159     {id: 2, name: 'Jamie', age: 37},
22160     {id: 3, name: 'Abe',   age: 32},
22161     {id: 4, name: 'Aaron', age: 26},
22162     {id: 5, name: 'David', age: 32}
22163 ]);
22164
22165 var ageFilter = new Ext.util.Filter({
22166     property: 'age',
22167     value   : 32
22168 });
22169
22170 var longNameFilter = new Ext.util.Filter({
22171     filterFn: function(item) {
22172         return item.name.length > 4;
22173     }
22174 });
22175
22176 //a new MixedCollection with the 3 names longer than 4 characters
22177 var longNames = allNames.filter(longNameFilter);
22178
22179 //a new MixedCollection with the 2 people of age 24:
22180 var youngFolk = allNames.filter(ageFilter);
22181 </code></pre>
22182  * @constructor
22183  * @param {Object} config Config object
22184  */
22185 Ext.define('Ext.util.Filter', {
22186
22187     /* Begin Definitions */
22188
22189     /* End Definitions */
22190     /**
22191      * @cfg {String} property The property to filter on. Required unless a {@link #filter} is passed
22192      */
22193     
22194     /**
22195      * @cfg {Function} filterFn A custom filter function which is passed each item in the {@link Ext.util.MixedCollection} 
22196      * in turn. Should return true to accept each item or false to reject it
22197      */
22198     
22199     /**
22200      * @cfg {Boolean} anyMatch True to allow any match - no regex start/end line anchors will be added. Defaults to false
22201      */
22202     anyMatch: false,
22203     
22204     /**
22205      * @cfg {Boolean} exactMatch True to force exact match (^ and $ characters added to the regex). Defaults to false.
22206      * Ignored if anyMatch is true.
22207      */
22208     exactMatch: false,
22209     
22210     /**
22211      * @cfg {Boolean} caseSensitive True to make the regex case sensitive (adds 'i' switch to regex). Defaults to false.
22212      */
22213     caseSensitive: false,
22214     
22215     /**
22216      * @cfg {String} root Optional root property. This is mostly useful when filtering a Store, in which case we set the
22217      * root to 'data' to make the filter pull the {@link #property} out of the data object of each item
22218      */
22219     
22220     constructor: function(config) {
22221         Ext.apply(this, config);
22222         
22223         //we're aliasing filter to filterFn mostly for API cleanliness reasons, despite the fact it dirties the code here.
22224         //Ext.util.Sorter takes a sorterFn property but allows .sort to be called - we do the same here
22225         this.filter = this.filter || this.filterFn;
22226         
22227         if (this.filter == undefined) {
22228             if (this.property == undefined || this.value == undefined) {
22229                 // Commented this out temporarily because it stops us using string ids in models. TODO: Remove this once
22230                 // Model has been updated to allow string ids
22231                 
22232                 // Ext.Error.raise("A Filter requires either a property or a filterFn to be set");
22233             } else {
22234                 this.filter = this.createFilterFn();
22235             }
22236             
22237             this.filterFn = this.filter;
22238         }
22239     },
22240     
22241     /**
22242      * @private
22243      * Creates a filter function for the configured property/value/anyMatch/caseSensitive options for this Filter
22244      */
22245     createFilterFn: function() {
22246         var me       = this,
22247             matcher  = me.createValueMatcher(),
22248             property = me.property;
22249         
22250         return function(item) {
22251             return matcher.test(me.getRoot.call(me, item)[property]);
22252         };
22253     },
22254     
22255     /**
22256      * @private
22257      * Returns the root property of the given item, based on the configured {@link #root} property
22258      * @param {Object} item The item
22259      * @return {Object} The root property of the object
22260      */
22261     getRoot: function(item) {
22262         return this.root == undefined ? item : item[this.root];
22263     },
22264     
22265     /**
22266      * @private
22267      * Returns a regular expression based on the given value and matching options
22268      */
22269     createValueMatcher : function() {
22270         var me            = this,
22271             value         = me.value,
22272             anyMatch      = me.anyMatch,
22273             exactMatch    = me.exactMatch,
22274             caseSensitive = me.caseSensitive,
22275             escapeRe      = Ext.String.escapeRegex;
22276         
22277         if (!value.exec) { // not a regex
22278             value = String(value);
22279
22280             if (anyMatch === true) {
22281                 value = escapeRe(value);
22282             } else {
22283                 value = '^' + escapeRe(value);
22284                 if (exactMatch === true) {
22285                     value += '$';
22286                 }
22287             }
22288             value = new RegExp(value, caseSensitive ? '' : 'i');
22289          }
22290          
22291          return value;
22292     }
22293 });
22294 /**
22295  * @class Ext.util.Sorter
22296  * @extends Object
22297  * Represents a single sorter that can be applied to a Store
22298  */
22299 Ext.define('Ext.util.Sorter', {
22300
22301     /**
22302      * @cfg {String} property The property to sort by. Required unless {@link #sorter} is provided
22303      */
22304     
22305     /**
22306      * @cfg {Function} sorterFn A specific sorter function to execute. Can be passed instead of {@link #property}
22307      */
22308     
22309     /**
22310      * @cfg {String} root Optional root property. This is mostly useful when sorting a Store, in which case we set the
22311      * root to 'data' to make the filter pull the {@link #property} out of the data object of each item
22312      */
22313     
22314     /**
22315      * @cfg {Function} transform A function that will be run on each value before
22316      * it is compared in the sorter. The function will receive a single argument,
22317      * the value.
22318      */
22319     
22320     /**
22321      * @cfg {String} direction The direction to sort by. Defaults to ASC
22322      */
22323     direction: "ASC",
22324     
22325     constructor: function(config) {
22326         var me = this;
22327         
22328         Ext.apply(me, config);
22329         
22330         if (me.property == undefined && me.sorterFn == undefined) {
22331             Ext.Error.raise("A Sorter requires either a property or a sorter function");
22332         }
22333         
22334         me.updateSortFunction();
22335     },
22336     
22337     /**
22338      * @private
22339      * Creates and returns a function which sorts an array by the given property and direction
22340      * @return {Function} A function which sorts by the property/direction combination provided
22341      */
22342     createSortFunction: function(sorterFn) {
22343         var me        = this,
22344             property  = me.property,
22345             direction = me.direction || "ASC",
22346             modifier  = direction.toUpperCase() == "DESC" ? -1 : 1;
22347         
22348         //create a comparison function. Takes 2 objects, returns 1 if object 1 is greater,
22349         //-1 if object 2 is greater or 0 if they are equal
22350         return function(o1, o2) {
22351             return modifier * sorterFn.call(me, o1, o2);
22352         };
22353     },
22354     
22355     /**
22356      * @private
22357      * Basic default sorter function that just compares the defined property of each object
22358      */
22359     defaultSorterFn: function(o1, o2) {
22360         var me = this,
22361             transform = me.transform,
22362             v1 = me.getRoot(o1)[me.property],
22363             v2 = me.getRoot(o2)[me.property];
22364             
22365         if (transform) {
22366             v1 = transform(v1);
22367             v2 = transform(v2);
22368         }
22369
22370         return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0);
22371     },
22372     
22373     /**
22374      * @private
22375      * Returns the root property of the given item, based on the configured {@link #root} property
22376      * @param {Object} item The item
22377      * @return {Object} The root property of the object
22378      */
22379     getRoot: function(item) {
22380         return this.root == undefined ? item : item[this.root];
22381     },
22382     
22383     // @TODO: Add docs for these three methods
22384     setDirection: function(direction) {
22385         var me = this;
22386         me.direction = direction;
22387         me.updateSortFunction();
22388     },
22389     
22390     toggle: function() {
22391         var me = this;
22392         me.direction = Ext.String.toggle(me.direction, "ASC", "DESC");
22393         me.updateSortFunction();
22394     },
22395     
22396     updateSortFunction: function() {
22397         var me = this;
22398         me.sort = me.createSortFunction(me.sorterFn || me.defaultSorterFn);
22399     }
22400 });
22401 /**
22402  * @class Ext.ElementLoader
22403  * A class used to load remote content to an Element. Sample usage:
22404  * <pre><code>
22405 Ext.get('el').load({
22406     url: 'myPage.php',
22407     scripts: true,
22408     params: {
22409         id: 1
22410     }
22411 });
22412  * </code></pre>
22413  * <p>
22414  * In general this class will not be instanced directly, rather the {@link Ext.core.Element#load} method
22415  * will be used.
22416  * </p>
22417  */
22418 Ext.define('Ext.ElementLoader', {
22419
22420     /* Begin Definitions */
22421
22422     mixins: {
22423         observable: 'Ext.util.Observable'
22424     },
22425
22426     uses: [
22427         'Ext.data.Connection',
22428         'Ext.Ajax'
22429     ],
22430     
22431     statics: {
22432         Renderer: {
22433             Html: function(loader, response, active){
22434                 loader.getTarget().update(response.responseText, active.scripts === true);
22435                 return true;
22436             }
22437         }     
22438     },
22439
22440     /* End Definitions */
22441
22442     /**
22443      * @cfg {String} url The url to retrieve the content from. Defaults to <tt>null</tt>.
22444      */
22445     url: null,
22446
22447     /**
22448      * @cfg {Object} params Any params to be attached to the Ajax request. These parameters will
22449      * be overridden by any params in the load options. Defaults to <tt>null</tt>.
22450      */
22451     params: null,
22452
22453     /**
22454      * @cfg {Object} baseParams Params that will be attached to every request. These parameters
22455      * will not be overridden by any params in the load options. Defaults to <tt>null</tt>.
22456      */
22457     baseParams: null,
22458
22459     /**
22460      * @cfg {Boolean/Object} autoLoad True to have the loader make a request as soon as it is created. Defaults to <tt>false</tt>.
22461      * This argument can also be a set of options that will be passed to {@link #load} is called.
22462      */
22463     autoLoad: false,
22464
22465     /**
22466      * @cfg {Mixed} target The target element for the loader. It can be the DOM element, the id or an Ext.Element.
22467      */
22468     target: null,
22469
22470     /**
22471      * @cfg {Mixed} loadMask True or a string to show when the element is loading.
22472      */
22473     loadMask: false,
22474
22475     /**
22476      * @cfg {Object} ajaxOptions Any additional options to be passed to the request, for example timeout or headers. Defaults to <tt>null</tt>.
22477      */
22478     ajaxOptions: null,
22479     
22480     /**
22481      * @cfg {Boolean} scripts True to parse any inline script tags in the response.
22482      */
22483     scripts: false,
22484
22485     /**
22486      * @cfg {Function} success A function to be called when a load request is successful.
22487      */
22488
22489     /**
22490      * @cfg {Function} failure A function to be called when a load request fails.
22491      */
22492
22493     /**
22494      * @cfg {Object} scope The scope to execute the {@link #success} and {@link #failure} functions in.
22495      */
22496     
22497     /**
22498      * @cfg {Function} renderer A custom function to render the content to the element. The passed parameters
22499      * are
22500      * <ul>
22501      * <li>The loader</li>
22502      * <li>The response</li>
22503      * <li>The active request</li>
22504      * </ul>
22505      */
22506
22507     isLoader: true,
22508
22509     constructor: function(config) {
22510         var me = this,
22511             autoLoad;
22512         
22513         config = config || {};
22514         Ext.apply(me, config);
22515         me.setTarget(me.target);
22516         me.addEvents(
22517             /**
22518              * @event beforeload
22519              * Fires before a load request is made to the server.
22520              * Returning false from an event listener can prevent the load
22521              * from occurring.
22522              * @param {Ext.ElementLoader} this
22523              * @param {Object} options The options passed to the request
22524              */
22525             'beforeload',
22526
22527             /**
22528              * @event exception
22529              * Fires after an unsuccessful load.
22530              * @param {Ext.ElementLoader} this
22531              * @param {Object} response The response from the server
22532              * @param {Object} options The options passed to the request
22533              */
22534             'exception',
22535
22536             /**
22537              * @event exception
22538              * Fires after a successful load.
22539              * @param {Ext.ElementLoader} this
22540              * @param {Object} response The response from the server
22541              * @param {Object} options The options passed to the request
22542              */
22543             'load'
22544         );
22545
22546         // don't pass config because we have already applied it.
22547         me.mixins.observable.constructor.call(me);
22548
22549         if (me.autoLoad) {
22550             autoLoad = me.autoLoad;
22551             if (autoLoad === true) {
22552                 autoLoad = {};
22553             }
22554             me.load(autoLoad);
22555         }
22556     },
22557
22558     /**
22559      * Set an {Ext.Element} as the target of this loader. Note that if the target is changed,
22560      * any active requests will be aborted.
22561      * @param {Mixed} target The element
22562      */
22563     setTarget: function(target){
22564         var me = this;
22565         target = Ext.get(target);
22566         if (me.target && me.target != target) {
22567             me.abort();
22568         }
22569         me.target = target;
22570     },
22571
22572     /**
22573      * Get the target of this loader.
22574      * @return {Ext.Component} target The target, null if none exists.
22575      */
22576     getTarget: function(){
22577         return this.target || null;
22578     },
22579
22580     /**
22581      * Aborts the active load request
22582      */
22583     abort: function(){
22584         var active = this.active;
22585         if (active !== undefined) {
22586             Ext.Ajax.abort(active.request);
22587             if (active.mask) {
22588                 this.removeMask();
22589             }
22590             delete this.active;
22591         }
22592     },
22593     
22594     /**
22595      * Remove the mask on the target
22596      * @private
22597      */
22598     removeMask: function(){
22599         this.target.unmask();
22600     },
22601     
22602     /**
22603      * Add the mask on the target
22604      * @private
22605      * @param {Mixed} mask The mask configuration
22606      */
22607     addMask: function(mask){
22608         this.target.mask(mask === true ? null : mask);
22609     },
22610
22611     /**
22612      * Load new data from the server.
22613      * @param {Object} options The options for the request. They can be any configuration option that can be specified for
22614      * the class, with the exception of the target option. Note that any options passed to the method will override any
22615      * class defaults.
22616      */
22617     load: function(options) {
22618         if (!this.target) {
22619             Ext.Error.raise('A valid target is required when loading content');
22620         }
22621
22622         options = Ext.apply({}, options);
22623
22624         var me = this,
22625             target = me.target,
22626             mask = Ext.isDefined(options.loadMask) ? options.loadMask : me.loadMask,
22627             params = Ext.apply({}, options.params),
22628             ajaxOptions = Ext.apply({}, options.ajaxOptions),
22629             callback = options.callback || me.callback,
22630             scope = options.scope || me.scope || me,
22631             request;
22632
22633         Ext.applyIf(ajaxOptions, me.ajaxOptions);
22634         Ext.applyIf(options, ajaxOptions);
22635
22636         Ext.applyIf(params, me.params);
22637         Ext.apply(params, me.baseParams);
22638
22639         Ext.applyIf(options, {
22640             url: me.url
22641         });
22642
22643         if (!options.url) {
22644             Ext.Error.raise('You must specify the URL from which content should be loaded');
22645         }
22646
22647         Ext.apply(options, {
22648             scope: me,
22649             params: params,
22650             callback: me.onComplete
22651         });
22652
22653         if (me.fireEvent('beforeload', me, options) === false) {
22654             return;
22655         }
22656
22657         if (mask) {
22658             me.addMask(mask);
22659         }
22660
22661         request = Ext.Ajax.request(options);
22662         me.active = {
22663             request: request,
22664             options: options,
22665             mask: mask,
22666             scope: scope,
22667             callback: callback,
22668             success: options.success || me.success,
22669             failure: options.failure || me.failure,
22670             renderer: options.renderer || me.renderer,
22671             scripts: Ext.isDefined(options.scripts) ? options.scripts : me.scripts
22672         };
22673         me.setOptions(me.active, options);
22674     },
22675     
22676     /**
22677      * Set any additional options on the active request
22678      * @private
22679      * @param {Object} active The active request
22680      * @param {Object} options The initial options
22681      */
22682     setOptions: Ext.emptyFn,
22683
22684     /**
22685      * Parse the response after the request completes
22686      * @private
22687      * @param {Object} options Ajax options
22688      * @param {Boolean} success Success status of the request
22689      * @param {Object} response The response object
22690      */
22691     onComplete: function(options, success, response) {
22692         var me = this,
22693             active = me.active,
22694             scope = active.scope,
22695             renderer = me.getRenderer(active.renderer);
22696
22697
22698         if (success) {
22699             success = renderer.call(me, me, response, active);
22700         }
22701
22702         if (success) {
22703             Ext.callback(active.success, scope, [me, response, options]);
22704             me.fireEvent('load', me, response, options);
22705         } else {
22706             Ext.callback(active.failure, scope, [me, response, options]);
22707             me.fireEvent('exception', me, response, options);
22708         }
22709         Ext.callback(active.callback, scope, [me, success, response, options]);
22710
22711         if (active.mask) {
22712             me.removeMask();
22713         }
22714
22715         delete me.active;
22716     },
22717
22718     /**
22719      * Gets the renderer to use
22720      * @private
22721      * @param {String/Function} renderer The renderer to use
22722      * @return {Function} A rendering function to use.
22723      */
22724     getRenderer: function(renderer){
22725         if (Ext.isFunction(renderer)) {
22726             return renderer;
22727         }
22728         return this.statics().Renderer.Html;
22729     },
22730     
22731     /**
22732      * Automatically refreshes the content over a specified period.
22733      * @param {Number} interval The interval to refresh in ms.
22734      * @param {Object} options (optional) The options to pass to the load method. See {@link #load}
22735      */
22736     startAutoRefresh: function(interval, options){
22737         var me = this;
22738         me.stopAutoRefresh();
22739         me.autoRefresh = setInterval(function(){
22740             me.load(options);
22741         }, interval);
22742     },
22743     
22744     /**
22745      * Clears any auto refresh. See {@link #startAutoRefresh}.
22746      */
22747     stopAutoRefresh: function(){
22748         clearInterval(this.autoRefresh);
22749         delete this.autoRefresh;
22750     },
22751     
22752     /**
22753      * Checks whether the loader is automatically refreshing. See {@link #startAutoRefresh}.
22754      * @return {Boolean} True if the loader is automatically refreshing
22755      */
22756     isAutoRefreshing: function(){
22757         return Ext.isDefined(this.autoRefresh);
22758     },
22759
22760     /**
22761      * Destroys the loader. Any active requests will be aborted.
22762      */
22763     destroy: function(){
22764         var me = this;
22765         me.stopAutoRefresh();
22766         delete me.target;
22767         me.abort();
22768         me.clearListeners();
22769     }
22770 });
22771
22772 /**
22773  * @class Ext.layout.Layout
22774  * @extends Object
22775  * @private
22776  * Base Layout class - extended by ComponentLayout and ContainerLayout
22777  */
22778
22779 Ext.define('Ext.layout.Layout', {
22780
22781     /* Begin Definitions */
22782
22783     /* End Definitions */
22784
22785     isLayout: true,
22786     initialized: false,
22787
22788     statics: {
22789         create: function(layout, defaultType) {
22790             var type;
22791             if (layout instanceof Ext.layout.Layout) {
22792                 return Ext.createByAlias('layout.' + layout);
22793             } else {
22794                 if (Ext.isObject(layout)) {
22795                     type = layout.type;
22796                 }
22797                 else {
22798                     type = layout || defaultType;
22799                     layout = {};
22800                 }
22801                 return Ext.createByAlias('layout.' + type, layout || {});
22802             }
22803         }
22804     },
22805
22806     constructor : function(config) {
22807         this.id = Ext.id(null, this.type + '-');
22808         Ext.apply(this, config);
22809     },
22810
22811     /**
22812      * @private
22813      */
22814     layout : function() {
22815         var me = this;
22816         me.layoutBusy = true;
22817         me.initLayout();
22818
22819         if (me.beforeLayout.apply(me, arguments) !== false) {
22820             me.layoutCancelled = false;
22821             me.onLayout.apply(me, arguments);
22822             me.childrenChanged = false;
22823             me.owner.needsLayout = false;
22824             me.layoutBusy = false;
22825             me.afterLayout.apply(me, arguments);
22826         }
22827         else {
22828             me.layoutCancelled = true;
22829         }
22830         me.layoutBusy = false;
22831         me.doOwnerCtLayouts();
22832     },
22833
22834     beforeLayout : function() {
22835         this.renderItems(this.getLayoutItems(), this.getRenderTarget());
22836         return true;
22837     },
22838
22839     /**
22840      * @private
22841      * Iterates over all passed items, ensuring they are rendered.  If the items are already rendered,
22842      * also determines if the items are in the proper place dom.
22843      */
22844     renderItems : function(items, target) {
22845         var ln = items.length,
22846             i = 0,
22847             item;
22848
22849         for (; i < ln; i++) {
22850             item = items[i];
22851             if (item && !item.rendered) {
22852                 this.renderItem(item, target, i);
22853             }
22854             else if (!this.isValidParent(item, target, i)) {
22855                 this.moveItem(item, target, i);
22856             }
22857         }
22858     },
22859
22860     // @private - Validates item is in the proper place in the dom.
22861     isValidParent : function(item, target, position) {
22862         var dom = item.el ? item.el.dom : Ext.getDom(item);
22863         if (dom && target && target.dom) {
22864             if (Ext.isNumber(position) && dom !== target.dom.childNodes[position]) {
22865                 return false;
22866             }
22867             return (dom.parentNode == (target.dom || target));
22868         }
22869         return false;
22870     },
22871
22872     /**
22873      * @private
22874      * Renders the given Component into the target Element.
22875      * @param {Ext.Component} item The Component to render
22876      * @param {Ext.core.Element} target The target Element
22877      * @param {Number} position The position within the target to render the item to
22878      */
22879     renderItem : function(item, target, position) {
22880         if (!item.rendered) {
22881             item.render(target, position);
22882             this.configureItem(item);
22883             this.childrenChanged = true;
22884         }
22885     },
22886
22887     /**
22888      * @private
22889      * Moved Component to the provided target instead.
22890      */
22891     moveItem : function(item, target, position) {
22892         // Make sure target is a dom element
22893         target = target.dom || target;
22894         if (typeof position == 'number') {
22895             position = target.childNodes[position];
22896         }
22897         target.insertBefore(item.el.dom, position || null);
22898         item.container = Ext.get(target);
22899         this.configureItem(item);
22900         this.childrenChanged = true;
22901     },
22902
22903     /**
22904      * @private
22905      * Adds the layout's targetCls if necessary and sets
22906      * initialized flag when complete.
22907      */
22908     initLayout : function() {
22909         if (!this.initialized && !Ext.isEmpty(this.targetCls)) {
22910             this.getTarget().addCls(this.targetCls);
22911         }
22912         this.initialized = true;
22913     },
22914
22915     // @private Sets the layout owner
22916     setOwner : function(owner) {
22917         this.owner = owner;
22918     },
22919
22920     // @private - Returns empty array
22921     getLayoutItems : function() {
22922         return [];
22923     },
22924
22925     /**
22926      * @private
22927      * Applies itemCls
22928      */
22929     configureItem: function(item) {
22930         var me = this,
22931             el = item.el,
22932             owner = me.owner;
22933             
22934         if (me.itemCls) {
22935             el.addCls(me.itemCls);
22936         }
22937         if (owner.itemCls) {
22938             el.addCls(owner.itemCls);
22939         }
22940     },
22941     
22942     // Placeholder empty functions for subclasses to extend
22943     onLayout : Ext.emptyFn,
22944     afterLayout : Ext.emptyFn,
22945     onRemove : Ext.emptyFn,
22946     onDestroy : Ext.emptyFn,
22947     doOwnerCtLayouts : Ext.emptyFn,
22948
22949     /**
22950      * @private
22951      * Removes itemCls
22952      */
22953     afterRemove : function(item) {
22954         var me = this,
22955             el = item.el,
22956             owner = me.owner;
22957             
22958         if (item.rendered) {
22959             if (me.itemCls) {
22960                 el.removeCls(me.itemCls);
22961             }
22962             if (owner.itemCls) {
22963                 el.removeCls(owner.itemCls);
22964             }
22965         }
22966     },
22967
22968     /*
22969      * Destroys this layout. This is a template method that is empty by default, but should be implemented
22970      * by subclasses that require explicit destruction to purge event handlers or remove DOM nodes.
22971      * @protected
22972      */
22973     destroy : function() {
22974         if (!Ext.isEmpty(this.targetCls)) {
22975             var target = this.getTarget();
22976             if (target) {
22977                 target.removeCls(this.targetCls);
22978             }
22979         }
22980         this.onDestroy();
22981     }
22982 });
22983 /**
22984  * @class Ext.layout.component.Component
22985  * @extends Ext.layout.Layout
22986  * @private
22987  * <p>This class is intended to be extended or created via the <tt><b>{@link Ext.Component#componentLayout layout}</b></tt>
22988  * configuration property.  See <tt><b>{@link Ext.Component#componentLayout}</b></tt> for additional details.</p>
22989  */
22990
22991 Ext.define('Ext.layout.component.Component', {
22992
22993     /* Begin Definitions */
22994
22995     extend: 'Ext.layout.Layout',
22996
22997     /* End Definitions */
22998
22999     type: 'component',
23000
23001     monitorChildren: true,
23002
23003     initLayout : function() {
23004         var me = this,
23005             owner = me.owner,
23006             ownerEl = owner.el;
23007
23008         if (!me.initialized) {
23009             if (owner.frameSize) {
23010                 me.frameSize = owner.frameSize;
23011             }
23012             else {
23013                 owner.frameSize = me.frameSize = {
23014                     top: 0,
23015                     left: 0,
23016                     bottom: 0,
23017                     right: 0
23018                 }; 
23019             }
23020         }
23021         me.callParent(arguments);
23022     },
23023
23024     beforeLayout : function(width, height, isSetSize, layoutOwner) {
23025         this.callParent(arguments);
23026
23027         var me = this,
23028             owner = me.owner,
23029             ownerCt = owner.ownerCt,
23030             layout = owner.layout,
23031             isVisible = owner.isVisible(true),
23032             ownerElChild = owner.el.child,
23033             layoutCollection;
23034
23035         /**
23036         * Do not layout calculatedSized components for fixedLayouts unless the ownerCt == layoutOwner
23037         * fixedLayouts means layouts which are never auto/auto in the sizing that comes from their ownerCt.
23038         * Currently 3 layouts MAY be auto/auto (Auto, Border, and Box)
23039         * The reason for not allowing component layouts is to stop component layouts from things such as Updater and
23040         * form Validation.
23041         */
23042         if (!isSetSize && !(Ext.isNumber(width) && Ext.isNumber(height)) && ownerCt && ownerCt.layout && ownerCt.layout.fixedLayout && ownerCt != layoutOwner) {
23043             me.doContainerLayout();
23044             return false;
23045         }
23046
23047         // If an ownerCt is hidden, add my reference onto the layoutOnShow stack.  Set the needsLayout flag.
23048         // If the owner itself is a directly hidden floater, set the needsLayout object on that for when it is shown.
23049         if (!isVisible && (owner.hiddenAncestor || owner.floating)) {
23050             if (owner.hiddenAncestor) {
23051                 layoutCollection = owner.hiddenAncestor.layoutOnShow;
23052                 layoutCollection.remove(owner);
23053                 layoutCollection.add(owner);
23054             }
23055             owner.needsLayout = {
23056                 width: width,
23057                 height: height,
23058                 isSetSize: false
23059             };
23060         }
23061
23062         if (isVisible && this.needsLayout(width, height)) {
23063             me.rawWidth = width;
23064             me.rawHeight = height;
23065             return owner.beforeComponentLayout(width, height, isSetSize, layoutOwner);
23066         }
23067         else {
23068             return false;
23069         }
23070     },
23071
23072     /**
23073     * Check if the new size is different from the current size and only
23074     * trigger a layout if it is necessary.
23075     * @param {Mixed} width The new width to set.
23076     * @param {Mixed} height The new height to set.
23077     */
23078     needsLayout : function(width, height) {
23079         this.lastComponentSize = this.lastComponentSize || {
23080             width: -Infinity,
23081             height: -Infinity
23082         };
23083         return (this.childrenChanged || this.lastComponentSize.width !== width || this.lastComponentSize.height !== height);
23084     },
23085
23086     /**
23087     * Set the size of any element supporting undefined, null, and values.
23088     * @param {Mixed} width The new width to set.
23089     * @param {Mixed} height The new height to set.
23090     */
23091     setElementSize: function(el, width, height) {
23092         if (width !== undefined && height !== undefined) {
23093             el.setSize(width, height);
23094         }
23095         else if (height !== undefined) {
23096             el.setHeight(height);
23097         }
23098         else if (width !== undefined) {
23099             el.setWidth(width);
23100         }
23101     },
23102
23103     /**
23104      * Returns the owner component's resize element.
23105      * @return {Ext.core.Element}
23106      */
23107      getTarget : function() {
23108          return this.owner.el;
23109      },
23110
23111     /**
23112      * <p>Returns the element into which rendering must take place. Defaults to the owner Component's encapsulating element.</p>
23113      * May be overridden in Component layout managers which implement an inner element.
23114      * @return {Ext.core.Element}
23115      */
23116     getRenderTarget : function() {
23117         return this.owner.el;
23118     },
23119
23120     /**
23121     * Set the size of the target element.
23122     * @param {Mixed} width The new width to set.
23123     * @param {Mixed} height The new height to set.
23124     */
23125     setTargetSize : function(width, height) {
23126         var me = this;
23127         me.setElementSize(me.owner.el, width, height);
23128
23129         if (me.owner.frameBody) {
23130             var targetInfo = me.getTargetInfo(),
23131                 padding = targetInfo.padding,
23132                 border = targetInfo.border,
23133                 frameSize = me.frameSize;
23134
23135             me.setElementSize(me.owner.frameBody,
23136                 Ext.isNumber(width) ? (width - frameSize.left - frameSize.right - padding.left - padding.right - border.left - border.right) : width,
23137                 Ext.isNumber(height) ? (height - frameSize.top - frameSize.bottom - padding.top - padding.bottom - border.top - border.bottom) : height
23138             );
23139         }
23140
23141         me.autoSized = {
23142             width: !Ext.isNumber(width),
23143             height: !Ext.isNumber(height)
23144         };
23145
23146         me.lastComponentSize = {
23147             width: width,
23148             height: height
23149         };
23150     },
23151
23152     getTargetInfo : function() {
23153         if (!this.targetInfo) {
23154             var target = this.getTarget(),
23155                 body = this.owner.getTargetEl();
23156
23157             this.targetInfo = {
23158                 padding: {
23159                     top: target.getPadding('t'),
23160                     right: target.getPadding('r'),
23161                     bottom: target.getPadding('b'),
23162                     left: target.getPadding('l')
23163                 },
23164                 border: {
23165                     top: target.getBorderWidth('t'),
23166                     right: target.getBorderWidth('r'),
23167                     bottom: target.getBorderWidth('b'),
23168                     left: target.getBorderWidth('l')
23169                 },
23170                 bodyMargin: {
23171                     top: body.getMargin('t'),
23172                     right: body.getMargin('r'),
23173                     bottom: body.getMargin('b'),
23174                     left: body.getMargin('l')
23175                 } 
23176             };
23177         }
23178         return this.targetInfo;
23179     },
23180
23181     // Start laying out UP the ownerCt's layout when flagged to do so.
23182     doOwnerCtLayouts: function() {
23183         var owner = this.owner,
23184             ownerCt = owner.ownerCt,
23185             ownerCtComponentLayout, ownerCtContainerLayout;
23186
23187         if (!ownerCt) {
23188             return;
23189         }
23190
23191         ownerCtComponentLayout = ownerCt.componentLayout;
23192         ownerCtContainerLayout = ownerCt.layout;
23193
23194         if (!owner.floating && ownerCtComponentLayout && ownerCtComponentLayout.monitorChildren && !ownerCtComponentLayout.layoutBusy) {
23195             if (!ownerCt.suspendLayout && ownerCtContainerLayout && !ownerCtContainerLayout.layoutBusy) {
23196                 // AutoContainer Layout and Dock with auto in some dimension
23197                 if (ownerCtContainerLayout.bindToOwnerCtComponent === true) {
23198                     ownerCt.doComponentLayout();
23199                 }
23200                 // Box Layouts
23201                 else if (ownerCtContainerLayout.bindToOwnerCtContainer === true) {
23202                     ownerCtContainerLayout.layout();
23203                 }
23204             }
23205         }
23206     },
23207
23208     doContainerLayout: function() {
23209         var me = this,
23210             owner = me.owner,
23211             ownerCt = owner.ownerCt,
23212             layout = owner.layout,
23213             ownerCtComponentLayout;
23214
23215         // Run the container layout if it exists (layout for child items)
23216         // **Unless automatic laying out is suspended, or the layout is currently running**
23217         if (!owner.suspendLayout && layout && layout.isLayout && !layout.layoutBusy) {
23218             layout.layout();
23219         }
23220
23221         // Tell the ownerCt that it's child has changed and can be re-layed by ignoring the lastComponentSize cache.
23222         if (ownerCt && ownerCt.componentLayout) {
23223             ownerCtComponentLayout = ownerCt.componentLayout;
23224             if (!owner.floating && ownerCtComponentLayout.monitorChildren && !ownerCtComponentLayout.layoutBusy) {
23225                 ownerCtComponentLayout.childrenChanged = true;
23226             }
23227         }
23228     },
23229
23230     afterLayout : function(width, height, isSetSize, layoutOwner) {
23231         this.doContainerLayout();
23232         this.owner.afterComponentLayout(width, height, isSetSize, layoutOwner);
23233     }
23234 });
23235
23236 /**
23237  * @class Ext.state.Manager
23238  * This is the global state manager. By default all components that are "state aware" check this class
23239  * for state information if you don't pass them a custom state provider. In order for this class
23240  * to be useful, it must be initialized with a provider when your application initializes. Example usage:
23241  <pre><code>
23242 // in your initialization function
23243 init : function(){
23244    Ext.state.Manager.setProvider(new Ext.state.CookieProvider());
23245    var win = new Window(...);
23246    win.restoreState();
23247 }
23248  </code></pre>
23249  * This class passes on calls from components to the underlying {@link Ext.state.Provider} so that
23250  * there is a common interface that can be used without needing to refer to a specific provider instance
23251  * in every component.
23252  * @singleton
23253  * @docauthor Evan Trimboli <evan@sencha.com>
23254  */
23255 Ext.define('Ext.state.Manager', {
23256     singleton: true,
23257     requires: ['Ext.state.Provider'],
23258     constructor: function() {
23259         this.provider = Ext.create('Ext.state.Provider');
23260     },
23261     
23262     
23263     /**
23264      * Configures the default state provider for your application
23265      * @param {Provider} stateProvider The state provider to set
23266      */
23267     setProvider : function(stateProvider){
23268         this.provider = stateProvider;
23269     },
23270
23271     /**
23272      * Returns the current value for a key
23273      * @param {String} name The key name
23274      * @param {Mixed} defaultValue The default value to return if the key lookup does not match
23275      * @return {Mixed} The state data
23276      */
23277     get : function(key, defaultValue){
23278         return this.provider.get(key, defaultValue);
23279     },
23280
23281     /**
23282      * Sets the value for a key
23283      * @param {String} name The key name
23284      * @param {Mixed} value The state data
23285      */
23286      set : function(key, value){
23287         this.provider.set(key, value);
23288     },
23289
23290     /**
23291      * Clears a value from the state
23292      * @param {String} name The key name
23293      */
23294     clear : function(key){
23295         this.provider.clear(key);
23296     },
23297
23298     /**
23299      * Gets the currently configured state provider
23300      * @return {Provider} The state provider
23301      */
23302     getProvider : function(){
23303         return this.provider;
23304     }
23305 });
23306 /**
23307  * @class Ext.state.Stateful
23308  * A mixin for being able to save the state of an object to an underlying 
23309  * {@link Ext.state.Provider}.
23310  */
23311 Ext.define('Ext.state.Stateful', {
23312     
23313     /* Begin Definitions */
23314    
23315    mixins: {
23316         observable: 'Ext.util.Observable'
23317     },
23318     
23319     requires: ['Ext.state.Manager'],
23320     
23321     /* End Definitions */
23322     
23323     /**
23324      * @cfg {Boolean} stateful
23325      * <p>A flag which causes the object to attempt to restore the state of
23326      * internal properties from a saved state on startup. The object must have
23327      * a <code>{@link #stateId}</code> for state to be managed. 
23328      * Auto-generated ids are not guaranteed to be stable across page loads and 
23329      * cannot be relied upon to save and restore the same state for a object.<p>
23330      * <p>For state saving to work, the state manager's provider must have been
23331      * set to an implementation of {@link Ext.state.Provider} which overrides the
23332      * {@link Ext.state.Provider#set set} and {@link Ext.state.Provider#get get}
23333      * methods to save and recall name/value pairs. A built-in implementation,
23334      * {@link Ext.state.CookieProvider} is available.</p>
23335      * <p>To set the state provider for the current page:</p>
23336      * <pre><code>
23337 Ext.state.Manager.setProvider(new Ext.state.CookieProvider({
23338     expires: new Date(new Date().getTime()+(1000*60*60*24*7)), //7 days from now
23339 }));
23340      * </code></pre>
23341      * <p>A stateful object attempts to save state when one of the events
23342      * listed in the <code>{@link #stateEvents}</code> configuration fires.</p>
23343      * <p>To save state, a stateful object first serializes its state by
23344      * calling <b><code>{@link #getState}</code></b>. By default, this function does
23345      * nothing. The developer must provide an implementation which returns an
23346      * object hash which represents the restorable state of the object.</p>
23347      * <p>The value yielded by getState is passed to {@link Ext.state.Manager#set}
23348      * which uses the configured {@link Ext.state.Provider} to save the object
23349      * keyed by the <code>{@link stateId}</code></p>.
23350      * <p>During construction, a stateful object attempts to <i>restore</i>
23351      * its state by calling {@link Ext.state.Manager#get} passing the
23352      * <code>{@link #stateId}</code></p>
23353      * <p>The resulting object is passed to <b><code>{@link #applyState}</code></b>.
23354      * The default implementation of <code>{@link #applyState}</code> simply copies
23355      * properties into the object, but a developer may override this to support
23356      * more behaviour.</p>
23357      * <p>You can perform extra processing on state save and restore by attaching
23358      * handlers to the {@link #beforestaterestore}, {@link #staterestore},
23359      * {@link #beforestatesave} and {@link #statesave} events.</p>
23360      */
23361     stateful: true,
23362     
23363     /**
23364      * @cfg {String} stateId
23365      * The unique id for this object to use for state management purposes.
23366      * <p>See {@link #stateful} for an explanation of saving and restoring state.</p>
23367      */
23368     
23369     /**
23370      * @cfg {Array} stateEvents
23371      * <p>An array of events that, when fired, should trigger this object to
23372      * save its state (defaults to none). <code>stateEvents</code> may be any type
23373      * of event supported by this object, including browser or custom events
23374      * (e.g., <tt>['click', 'customerchange']</tt>).</p>
23375      * <p>See <code>{@link #stateful}</code> for an explanation of saving and
23376      * restoring object state.</p>
23377      */
23378     
23379     /**
23380      * @cfg {Number} saveBuffer A buffer to be applied if many state events are fired within
23381      * a short period. Defaults to 100.
23382      */
23383     saveDelay: 100,
23384     
23385     autoGenIdRe: /^((\w+-)|(ext-comp-))\d{4,}$/i,
23386     
23387     constructor: function(config) {
23388         var me = this;
23389         
23390         config = config || {};
23391         if (Ext.isDefined(config.stateful)) {
23392             me.stateful = config.stateful;
23393         }
23394         if (Ext.isDefined(config.saveDelay)) {
23395             me.saveDelay = config.saveDelay;
23396         }
23397         me.stateId = config.stateId;
23398         
23399         if (!me.stateEvents) {
23400             me.stateEvents = [];
23401         }
23402         if (config.stateEvents) {
23403             me.stateEvents.concat(config.stateEvents);
23404         }
23405         this.addEvents(
23406             /**
23407              * @event beforestaterestore
23408              * Fires before the state of the object is restored. Return false from an event handler to stop the restore.
23409              * @param {Ext.state.Stateful} this
23410              * @param {Object} state The hash of state values returned from the StateProvider. If this
23411              * event is not vetoed, then the state object is passed to <b><tt>applyState</tt></b>. By default,
23412              * that simply copies property values into this object. The method maybe overriden to
23413              * provide custom state restoration.
23414              */
23415             'beforestaterestore',
23416             
23417             /**
23418              * @event staterestore
23419              * Fires after the state of the object is restored.
23420              * @param {Ext.state.Stateful} this
23421              * @param {Object} state The hash of state values returned from the StateProvider. This is passed
23422              * to <b><tt>applyState</tt></b>. By default, that simply copies property values into this
23423              * object. The method maybe overriden to provide custom state restoration.
23424              */
23425             'staterestore',
23426             
23427             /**
23428              * @event beforestatesave
23429              * Fires before the state of the object is saved to the configured state provider. Return false to stop the save.
23430              * @param {Ext.state.Stateful} this
23431              * @param {Object} state The hash of state values. This is determined by calling
23432              * <b><tt>getState()</tt></b> on the object. This method must be provided by the
23433              * developer to return whetever representation of state is required, by default, Ext.state.Stateful
23434              * has a null implementation.
23435              */
23436             'beforestatesave',
23437             
23438             /**
23439              * @event statesave
23440              * Fires after the state of the object is saved to the configured state provider.
23441              * @param {Ext.state.Stateful} this
23442              * @param {Object} state The hash of state values. This is determined by calling
23443              * <b><tt>getState()</tt></b> on the object. This method must be provided by the
23444              * developer to return whetever representation of state is required, by default, Ext.state.Stateful
23445              * has a null implementation.
23446              */
23447             'statesave'
23448         );
23449         me.mixins.observable.constructor.call(me);
23450         if (me.stateful !== false) {
23451             me.initStateEvents();
23452             me.initState();
23453         }
23454     },
23455     
23456     /**
23457      * Initializes any state events for this object.
23458      * @private
23459      */
23460     initStateEvents: function() {
23461         this.addStateEvents(this.stateEvents);
23462     },
23463     
23464     /**
23465      * Add events that will trigger the state to be saved.
23466      * @param {String/Array} events The event name or an array of event names.
23467      */
23468     addStateEvents: function(events){
23469         if (!Ext.isArray(events)) {
23470             events = [events];
23471         }
23472         
23473         var me = this,
23474             i = 0,
23475             len = events.length;
23476             
23477         for (; i < len; ++i) {
23478             me.on(events[i], me.onStateChange, me);
23479         }
23480     },
23481     
23482     /**
23483      * This method is called when any of the {@link #stateEvents} are fired.
23484      * @private
23485      */
23486     onStateChange: function(){
23487         var me = this,
23488             delay = me.saveDelay;
23489         
23490         if (delay > 0) {
23491             if (!me.stateTask) {
23492                 me.stateTask = Ext.create('Ext.util.DelayedTask', me.saveState, me);
23493             }
23494             me.stateTask.delay(me.saveDelay);
23495         } else {
23496             me.saveState();
23497         }
23498     },
23499     
23500     /**
23501      * Saves the state of the object to the persistence store.
23502      * @private
23503      */
23504     saveState: function() {
23505         var me = this,
23506             id,
23507             state;
23508         
23509         if (me.stateful !== false) {
23510             id = me.getStateId();
23511             if (id) {
23512                 state = me.getState();
23513                 if (me.fireEvent('beforestatesave', me, state) !== false) {
23514                     Ext.state.Manager.set(id, state);
23515                     me.fireEvent('statesave', me, state);
23516                 }
23517             }
23518         }
23519     },
23520     
23521     /**
23522      * Gets the current state of the object. By default this function returns null,
23523      * it should be overridden in subclasses to implement methods for getting the state.
23524      * @return {Object} The current state
23525      */
23526     getState: function(){
23527         return null;    
23528     },
23529     
23530     /**
23531      * Applies the state to the object. This should be overridden in subclasses to do
23532      * more complex state operations. By default it applies the state properties onto
23533      * the current object.
23534      * @param {Object} state The state
23535      */
23536     applyState: function(state) {
23537         if (state) {
23538             Ext.apply(this, state);
23539         }
23540     },
23541     
23542     /**
23543      * Gets the state id for this object.
23544      * @return {String} The state id, null if not found.
23545      */
23546     getStateId: function() {
23547         var me = this,
23548             id = me.stateId;
23549         
23550         if (!id) {
23551             id = me.autoGenIdRe.test(String(me.id)) ? null : me.id;
23552         }
23553         return id;
23554     },
23555     
23556     /**
23557      * Initializes the state of the object upon construction.
23558      * @private
23559      */
23560     initState: function(){
23561         var me = this,
23562             id = me.getStateId(),
23563             state;
23564             
23565         if (me.stateful !== false) {
23566             if (id) {
23567                 state = Ext.state.Manager.get(id);
23568                 if (state) {
23569                     state = Ext.apply({}, state);
23570                     if (me.fireEvent('beforestaterestore', me, state) !== false) {
23571                         me.applyState(state);
23572                         me.fireEvent('staterestore', me, state);
23573                     }
23574                 }
23575             }
23576         }
23577     },
23578     
23579     /**
23580      * Destroys this stateful object.
23581      */
23582     destroy: function(){
23583         var task = this.stateTask;
23584         if (task) {
23585             task.cancel();
23586         }
23587         this.clearListeners();
23588         
23589     }
23590     
23591 });
23592
23593 /**
23594  * @class Ext.AbstractManager
23595  * @extends Object
23596  * @ignore
23597  * Base Manager class
23598  */
23599
23600 Ext.define('Ext.AbstractManager', {
23601
23602     /* Begin Definitions */
23603
23604     requires: ['Ext.util.HashMap'],
23605
23606     /* End Definitions */
23607
23608     typeName: 'type',
23609
23610     constructor: function(config) {
23611         Ext.apply(this, config || {});
23612
23613         /**
23614          * Contains all of the items currently managed
23615          * @property all
23616          * @type Ext.util.MixedCollection
23617          */
23618         this.all = Ext.create('Ext.util.HashMap');
23619
23620         this.types = {};
23621     },
23622
23623     /**
23624      * Returns an item by id.
23625      * For additional details see {@link Ext.util.HashMap#get}.
23626      * @param {String} id The id of the item
23627      * @return {Mixed} The item, <code>undefined</code> if not found.
23628      */
23629     get : function(id) {
23630         return this.all.get(id);
23631     },
23632
23633     /**
23634      * Registers an item to be managed
23635      * @param {Mixed} item The item to register
23636      */
23637     register: function(item) {
23638         this.all.add(item);
23639     },
23640
23641     /**
23642      * Unregisters an item by removing it from this manager
23643      * @param {Mixed} item The item to unregister
23644      */
23645     unregister: function(item) {
23646         this.all.remove(item);
23647     },
23648
23649     /**
23650      * <p>Registers a new item constructor, keyed by a type key.
23651      * @param {String} type The mnemonic string by which the class may be looked up.
23652      * @param {Constructor} cls The new instance class.
23653      */
23654     registerType : function(type, cls) {
23655         this.types[type] = cls;
23656         cls[this.typeName] = type;
23657     },
23658
23659     /**
23660      * Checks if an item type is registered.
23661      * @param {String} type The mnemonic string by which the class may be looked up
23662      * @return {Boolean} Whether the type is registered.
23663      */
23664     isRegistered : function(type){
23665         return this.types[type] !== undefined;
23666     },
23667
23668     /**
23669      * Creates and returns an instance of whatever this manager manages, based on the supplied type and config object
23670      * @param {Object} config The config object
23671      * @param {String} defaultType If no type is discovered in the config object, we fall back to this type
23672      * @return {Mixed} The instance of whatever this manager is managing
23673      */
23674     create: function(config, defaultType) {
23675         var type        = config[this.typeName] || config.type || defaultType,
23676             Constructor = this.types[type];
23677
23678         if (Constructor == undefined) {
23679             Ext.Error.raise("The '" + type + "' type has not been registered with this manager");
23680         }
23681
23682         return new Constructor(config);
23683     },
23684
23685     /**
23686      * Registers a function that will be called when an item with the specified id is added to the manager. This will happen on instantiation.
23687      * @param {String} id The item id
23688      * @param {Function} fn The callback function. Called with a single parameter, the item.
23689      * @param {Object} scope The scope (<code>this</code> reference) in which the callback is executed. Defaults to the item.
23690      */
23691     onAvailable : function(id, fn, scope){
23692         var all = this.all,
23693             item;
23694         
23695         if (all.containsKey(id)) {
23696             item = all.get(id);
23697             fn.call(scope || item, item);
23698         } else {
23699             all.on('add', function(map, key, item){
23700                 if (key == id) {
23701                     fn.call(scope || item, item);
23702                     all.un('add', fn, scope);
23703                 }
23704             });
23705         }
23706     },
23707     
23708     /**
23709      * Executes the specified function once for each item in the collection.
23710      * Returning false from the function will cease iteration.
23711      * 
23712      * The paramaters passed to the function are:
23713      * <div class="mdetail-params"><ul>
23714      * <li><b>key</b> : String<p class="sub-desc">The key of the item</p></li>
23715      * <li><b>value</b> : Number<p class="sub-desc">The value of the item</p></li>
23716      * <li><b>length</b> : Number<p class="sub-desc">The total number of items in the collection</p></li>
23717      * </ul></div>
23718      * @param {Object} fn The function to execute.
23719      * @param {Object} scope The scope to execute in. Defaults to <tt>this</tt>.
23720      */
23721     each: function(fn, scope){
23722         this.all.each(fn, scope || this);    
23723     },
23724     
23725     /**
23726      * Gets the number of items in the collection.
23727      * @return {Number} The number of items in the collection.
23728      */
23729     getCount: function(){
23730         return this.all.getCount();
23731     }
23732 });
23733
23734 /**
23735  * @class Ext.PluginManager
23736  * @extends Ext.AbstractManager
23737  * <p>Provides a registry of available Plugin <i>classes</i> indexed by a mnemonic code known as the Plugin's ptype.
23738  * The <code>{@link Ext.Component#xtype xtype}</code> provides a way to avoid instantiating child Components
23739  * when creating a full, nested config object for a complete Ext page.</p>
23740  * <p>A child Component may be specified simply as a <i>config object</i>
23741  * as long as the correct <code>{@link Ext.Component#xtype xtype}</code> is specified so that if and when the Component
23742  * needs rendering, the correct type can be looked up for lazy instantiation.</p>
23743  * <p>For a list of all available <code>{@link Ext.Component#xtype xtypes}</code>, see {@link Ext.Component}.</p>
23744  * @singleton
23745  */
23746 Ext.define('Ext.PluginManager', {
23747     extend: 'Ext.AbstractManager',
23748     alternateClassName: 'Ext.PluginMgr',
23749     singleton: true,
23750     typeName: 'ptype',
23751
23752     /**
23753      * Creates a new Plugin from the specified config object using the
23754      * config object's ptype to determine the class to instantiate.
23755      * @param {Object} config A configuration object for the Plugin you wish to create.
23756      * @param {Constructor} defaultType The constructor to provide the default Plugin type if
23757      * the config object does not contain a <code>ptype</code>. (Optional if the config contains a <code>ptype</code>).
23758      * @return {Ext.Component} The newly instantiated Plugin.
23759      */
23760     //create: function(plugin, defaultType) {
23761     //    if (plugin instanceof this) {
23762     //        return plugin;
23763     //    } else {
23764     //        var type, config = {};
23765     //
23766     //        if (Ext.isString(plugin)) {
23767     //            type = plugin;
23768     //        }
23769     //        else {
23770     //            type = plugin[this.typeName] || defaultType;
23771     //            config = plugin;
23772     //        }
23773     //
23774     //        return Ext.createByAlias('plugin.' + type, config);
23775     //    }
23776     //},
23777
23778     create : function(config, defaultType){
23779         if (config.init) {
23780             return config;
23781         } else {
23782             return Ext.createByAlias('plugin.' + (config.ptype || defaultType), config);
23783         }
23784         
23785         // Prior system supported Singleton plugins.
23786         //var PluginCls = this.types[config.ptype || defaultType];
23787         //if (PluginCls.init) {
23788         //    return PluginCls;
23789         //} else {
23790         //    return new PluginCls(config);
23791         //}
23792     },
23793
23794     /**
23795      * Returns all plugins registered with the given type. Here, 'type' refers to the type of plugin, not its ptype.
23796      * @param {String} type The type to search for
23797      * @param {Boolean} defaultsOnly True to only return plugins of this type where the plugin's isDefault property is truthy
23798      * @return {Array} All matching plugins
23799      */
23800     findByType: function(type, defaultsOnly) {
23801         var matches = [],
23802             types   = this.types;
23803
23804         for (var name in types) {
23805             if (!types.hasOwnProperty(name)) {
23806                 continue;
23807             }
23808             var item = types[name];
23809
23810             if (item.type == type && (!defaultsOnly || (defaultsOnly === true && item.isDefault))) {
23811                 matches.push(item);
23812             }
23813         }
23814
23815         return matches;
23816     }
23817 }, function() {    
23818     /**
23819      * Shorthand for {@link Ext.PluginManager#registerType}
23820      * @param {String} ptype The ptype mnemonic string by which the Plugin class
23821      * may be looked up.
23822      * @param {Constructor} cls The new Plugin class.
23823      * @member Ext
23824      * @method preg
23825      */
23826     Ext.preg = function() {
23827         return Ext.PluginManager.registerType.apply(Ext.PluginManager, arguments);
23828     };
23829 });
23830
23831 /**
23832  * @class Ext.ComponentManager
23833  * @extends Ext.AbstractManager
23834  * <p>Provides a registry of all Components (instances of {@link Ext.Component} or any subclass
23835  * thereof) on a page so that they can be easily accessed by {@link Ext.Component component}
23836  * {@link Ext.Component#id id} (see {@link #get}, or the convenience method {@link Ext#getCmp Ext.getCmp}).</p>
23837  * <p>This object also provides a registry of available Component <i>classes</i>
23838  * indexed by a mnemonic code known as the Component's {@link Ext.Component#xtype xtype}.
23839  * The <code>xtype</code> provides a way to avoid instantiating child Components
23840  * when creating a full, nested config object for a complete Ext page.</p>
23841  * <p>A child Component may be specified simply as a <i>config object</i>
23842  * as long as the correct <code>{@link Ext.Component#xtype xtype}</code> is specified so that if and when the Component
23843  * needs rendering, the correct type can be looked up for lazy instantiation.</p>
23844  * <p>For a list of all available <code>{@link Ext.Component#xtype xtypes}</code>, see {@link Ext.Component}.</p>
23845  * @singleton
23846  */
23847 Ext.define('Ext.ComponentManager', {
23848     extend: 'Ext.AbstractManager',
23849     alternateClassName: 'Ext.ComponentMgr',
23850     
23851     singleton: true,
23852     
23853     typeName: 'xtype',
23854     
23855     /**
23856      * Creates a new Component from the specified config object using the
23857      * config object's xtype to determine the class to instantiate.
23858      * @param {Object} config A configuration object for the Component you wish to create.
23859      * @param {Constructor} defaultType The constructor to provide the default Component type if
23860      * the config object does not contain a <code>xtype</code>. (Optional if the config contains a <code>xtype</code>).
23861      * @return {Ext.Component} The newly instantiated Component.
23862      */
23863     create: function(component, defaultType){
23864         if (component instanceof Ext.AbstractComponent) {
23865             return component;
23866         }
23867         else if (Ext.isString(component)) {
23868             return Ext.createByAlias('widget.' + component);
23869         }
23870         else {
23871             var type = component.xtype || defaultType,
23872                 config = component;
23873             
23874             return Ext.createByAlias('widget.' + type, config);
23875         }
23876     },
23877
23878     registerType: function(type, cls) {
23879         this.types[type] = cls;
23880         cls[this.typeName] = type;
23881         cls.prototype[this.typeName] = type;
23882     }
23883 });
23884 /**
23885  * @class Ext.XTemplate
23886  * @extends Ext.Template
23887  * <p>A template class that supports advanced functionality like:<div class="mdetail-params"><ul>
23888  * <li>Autofilling arrays using templates and sub-templates</li>
23889  * <li>Conditional processing with basic comparison operators</li>
23890  * <li>Basic math function support</li>
23891  * <li>Execute arbitrary inline code with special built-in template variables</li>
23892  * <li>Custom member functions</li>
23893  * <li>Many special tags and built-in operators that aren't defined as part of
23894  * the API, but are supported in the templates that can be created</li>
23895  * </ul></div></p>
23896  * <p>XTemplate provides the templating mechanism built into:<div class="mdetail-params"><ul>
23897  * <li>{@link Ext.view.View}</li>
23898  * </ul></div></p>
23899  *
23900  * The {@link Ext.Template} describes
23901  * the acceptable parameters to pass to the constructor. The following
23902  * examples demonstrate all of the supported features.</p>
23903  *
23904  * <div class="mdetail-params"><ul>
23905  *
23906  * <li><b><u>Sample Data</u></b>
23907  * <div class="sub-desc">
23908  * <p>This is the data object used for reference in each code example:</p>
23909  * <pre><code>
23910 var data = {
23911 name: 'Tommy Maintz',
23912 title: 'Lead Developer',
23913 company: 'Sencha Inc.',
23914 email: 'tommy@sencha.com',
23915 address: '5 Cups Drive',
23916 city: 'Palo Alto',
23917 state: 'CA',
23918 zip: '44102',
23919 drinks: ['Coffee', 'Soda', 'Water'],
23920 kids: [{
23921         name: 'Joshua',
23922         age:3
23923     },{
23924         name: 'Matthew',
23925         age:2
23926     },{
23927         name: 'Solomon',
23928         age:0
23929 }]
23930 };
23931  </code></pre>
23932  * </div>
23933  * </li>
23934  *
23935  *
23936  * <li><b><u>Auto filling of arrays</u></b>
23937  * <div class="sub-desc">
23938  * <p>The <b><tt>tpl</tt></b> tag and the <b><tt>for</tt></b> operator are used
23939  * to process the provided data object:
23940  * <ul>
23941  * <li>If the value specified in <tt>for</tt> is an array, it will auto-fill,
23942  * repeating the template block inside the <tt>tpl</tt> tag for each item in the
23943  * array.</li>
23944  * <li>If <tt>for="."</tt> is specified, the data object provided is examined.</li>
23945  * <li>While processing an array, the special variable <tt>{#}</tt>
23946  * will provide the current array index + 1 (starts at 1, not 0).</li>
23947  * </ul>
23948  * </p>
23949  * <pre><code>
23950 &lt;tpl <b>for</b>=".">...&lt;/tpl>       // loop through array at root node
23951 &lt;tpl <b>for</b>="foo">...&lt;/tpl>     // loop through array at foo node
23952 &lt;tpl <b>for</b>="foo.bar">...&lt;/tpl> // loop through array at foo.bar node
23953  </code></pre>
23954  * Using the sample data above:
23955  * <pre><code>
23956 var tpl = new Ext.XTemplate(
23957     '&lt;p>Kids: ',
23958     '&lt;tpl <b>for</b>=".">',       // process the data.kids node
23959         '&lt;p>{#}. {name}&lt;/p>',  // use current array index to autonumber
23960     '&lt;/tpl>&lt;/p>'
23961 );
23962 tpl.overwrite(panel.body, data.kids); // pass the kids property of the data object
23963  </code></pre>
23964  * <p>An example illustrating how the <b><tt>for</tt></b> property can be leveraged
23965  * to access specified members of the provided data object to populate the template:</p>
23966  * <pre><code>
23967 var tpl = new Ext.XTemplate(
23968     '&lt;p>Name: {name}&lt;/p>',
23969     '&lt;p>Title: {title}&lt;/p>',
23970     '&lt;p>Company: {company}&lt;/p>',
23971     '&lt;p>Kids: ',
23972     '&lt;tpl <b>for="kids"</b>>',     // interrogate the kids property within the data
23973         '&lt;p>{name}&lt;/p>',
23974     '&lt;/tpl>&lt;/p>'
23975 );
23976 tpl.overwrite(panel.body, data);  // pass the root node of the data object
23977  </code></pre>
23978  * <p>Flat arrays that contain values (and not objects) can be auto-rendered
23979  * using the special <b><tt>{.}</tt></b> variable inside a loop.  This variable
23980  * will represent the value of the array at the current index:</p>
23981  * <pre><code>
23982 var tpl = new Ext.XTemplate(
23983     '&lt;p>{name}\&#39;s favorite beverages:&lt;/p>',
23984     '&lt;tpl for="drinks">',
23985         '&lt;div> - {.}&lt;/div>',
23986     '&lt;/tpl>'
23987 );
23988 tpl.overwrite(panel.body, data);
23989  </code></pre>
23990  * <p>When processing a sub-template, for example while looping through a child array,
23991  * you can access the parent object's members via the <b><tt>parent</tt></b> object:</p>
23992  * <pre><code>
23993 var tpl = new Ext.XTemplate(
23994     '&lt;p>Name: {name}&lt;/p>',
23995     '&lt;p>Kids: ',
23996     '&lt;tpl for="kids">',
23997         '&lt;tpl if="age &amp;gt; 1">',
23998             '&lt;p>{name}&lt;/p>',
23999             '&lt;p>Dad: {<b>parent</b>.name}&lt;/p>',
24000         '&lt;/tpl>',
24001     '&lt;/tpl>&lt;/p>'
24002 );
24003 tpl.overwrite(panel.body, data);
24004  </code></pre>
24005  * </div>
24006  * </li>
24007  *
24008  *
24009  * <li><b><u>Conditional processing with basic comparison operators</u></b>
24010  * <div class="sub-desc">
24011  * <p>The <b><tt>tpl</tt></b> tag and the <b><tt>if</tt></b> operator are used
24012  * to provide conditional checks for deciding whether or not to render specific
24013  * parts of the template. Notes:<div class="sub-desc"><ul>
24014  * <li>Double quotes must be encoded if used within the conditional</li>
24015  * <li>There is no <tt>else</tt> operator &mdash; if needed, two opposite
24016  * <tt>if</tt> statements should be used.</li>
24017  * </ul></div>
24018  * <pre><code>
24019 &lt;tpl if="age &gt; 1 &amp;&amp; age &lt; 10">Child&lt;/tpl>
24020 &lt;tpl if="age >= 10 && age < 18">Teenager&lt;/tpl>
24021 &lt;tpl <b>if</b>="this.isGirl(name)">...&lt;/tpl>
24022 &lt;tpl <b>if</b>="id==\'download\'">...&lt;/tpl>
24023 &lt;tpl <b>if</b>="needsIcon">&lt;img src="{icon}" class="{iconCls}"/>&lt;/tpl>
24024 // no good:
24025 &lt;tpl if="name == "Tommy"">Hello&lt;/tpl>
24026 // encode &#34; if it is part of the condition, e.g.
24027 &lt;tpl if="name == &#38;quot;Tommy&#38;quot;">Hello&lt;/tpl>
24028  * </code></pre>
24029  * Using the sample data above:
24030  * <pre><code>
24031 var tpl = new Ext.XTemplate(
24032     '&lt;p>Name: {name}&lt;/p>',
24033     '&lt;p>Kids: ',
24034     '&lt;tpl for="kids">',
24035         '&lt;tpl if="age &amp;gt; 1">',
24036             '&lt;p>{name}&lt;/p>',
24037         '&lt;/tpl>',
24038     '&lt;/tpl>&lt;/p>'
24039 );
24040 tpl.overwrite(panel.body, data);
24041  </code></pre>
24042  * </div>
24043  * </li>
24044  *
24045  *
24046  * <li><b><u>Basic math support</u></b>
24047  * <div class="sub-desc">
24048  * <p>The following basic math operators may be applied directly on numeric
24049  * data values:</p><pre>
24050  * + - * /
24051  * </pre>
24052  * For example:
24053  * <pre><code>
24054 var tpl = new Ext.XTemplate(
24055     '&lt;p>Name: {name}&lt;/p>',
24056     '&lt;p>Kids: ',
24057     '&lt;tpl for="kids">',
24058         '&lt;tpl if="age &amp;gt; 1">',  // <-- Note that the &gt; is encoded
24059             '&lt;p>{#}: {name}&lt;/p>',  // <-- Auto-number each item
24060             '&lt;p>In 5 Years: {age+5}&lt;/p>',  // <-- Basic math
24061             '&lt;p>Dad: {parent.name}&lt;/p>',
24062         '&lt;/tpl>',
24063     '&lt;/tpl>&lt;/p>'
24064 );
24065 tpl.overwrite(panel.body, data);
24066  </code></pre>
24067  * </div>
24068  * </li>
24069  *
24070  *
24071  * <li><b><u>Execute arbitrary inline code with special built-in template variables</u></b>
24072  * <div class="sub-desc">
24073  * <p>Anything between <code>{[ ... ]}</code> is considered code to be executed
24074  * in the scope of the template. There are some special variables available in that code:
24075  * <ul>
24076  * <li><b><tt>values</tt></b>: The values in the current scope. If you are using
24077  * scope changing sub-templates, you can change what <tt>values</tt> is.</li>
24078  * <li><b><tt>parent</tt></b>: The scope (values) of the ancestor template.</li>
24079  * <li><b><tt>xindex</tt></b>: If you are in a looping template, the index of the
24080  * loop you are in (1-based).</li>
24081  * <li><b><tt>xcount</tt></b>: If you are in a looping template, the total length
24082  * of the array you are looping.</li>
24083  * </ul>
24084  * This example demonstrates basic row striping using an inline code block and the
24085  * <tt>xindex</tt> variable:</p>
24086  * <pre><code>
24087 var tpl = new Ext.XTemplate(
24088     '&lt;p>Name: {name}&lt;/p>',
24089     '&lt;p>Company: {[values.company.toUpperCase() + ", " + values.title]}&lt;/p>',
24090     '&lt;p>Kids: ',
24091     '&lt;tpl for="kids">',
24092         '&lt;div class="{[xindex % 2 === 0 ? "even" : "odd"]}">',
24093         '{name}',
24094         '&lt;/div>',
24095     '&lt;/tpl>&lt;/p>'
24096  );
24097 tpl.overwrite(panel.body, data);
24098  </code></pre>
24099  * </div>
24100  * </li>
24101  *
24102  * <li><b><u>Template member functions</u></b>
24103  * <div class="sub-desc">
24104  * <p>One or more member functions can be specified in a configuration
24105  * object passed into the XTemplate constructor for more complex processing:</p>
24106  * <pre><code>
24107 var tpl = new Ext.XTemplate(
24108     '&lt;p>Name: {name}&lt;/p>',
24109     '&lt;p>Kids: ',
24110     '&lt;tpl for="kids">',
24111         '&lt;tpl if="this.isGirl(name)">',
24112             '&lt;p>Girl: {name} - {age}&lt;/p>',
24113         '&lt;/tpl>',
24114          // use opposite if statement to simulate 'else' processing:
24115         '&lt;tpl if="this.isGirl(name) == false">',
24116             '&lt;p>Boy: {name} - {age}&lt;/p>',
24117         '&lt;/tpl>',
24118         '&lt;tpl if="this.isBaby(age)">',
24119             '&lt;p>{name} is a baby!&lt;/p>',
24120         '&lt;/tpl>',
24121     '&lt;/tpl>&lt;/p>',
24122     {
24123         // XTemplate configuration:
24124         compiled: true,
24125         // member functions:
24126         isGirl: function(name){
24127            return name == 'Sara Grace';
24128         },
24129         isBaby: function(age){
24130            return age < 1;
24131         }
24132     }
24133 );
24134 tpl.overwrite(panel.body, data);
24135  </code></pre>
24136  * </div>
24137  * </li>
24138  *
24139  * </ul></div>
24140  *
24141  * @param {Mixed} config
24142  */
24143
24144 Ext.define('Ext.XTemplate', {
24145
24146     /* Begin Definitions */
24147
24148     extend: 'Ext.Template',
24149
24150     statics: {
24151         /**
24152          * Creates a template from the passed element's value (<i>display:none</i> textarea, preferred) or innerHTML.
24153          * @param {String/HTMLElement} el A DOM element or its id
24154          * @return {Ext.Template} The created template
24155          * @static
24156          */
24157         from: function(el, config) {
24158             el = Ext.getDom(el);
24159             return new this(el.value || el.innerHTML, config || {});
24160         }
24161     },
24162
24163     /* End Definitions */
24164
24165     argsRe: /<tpl\b[^>]*>((?:(?=([^<]+))\2|<(?!tpl\b[^>]*>))*?)<\/tpl>/,
24166     nameRe: /^<tpl\b[^>]*?for="(.*?)"/,
24167     ifRe: /^<tpl\b[^>]*?if="(.*?)"/,
24168     execRe: /^<tpl\b[^>]*?exec="(.*?)"/,
24169     constructor: function() {
24170         this.callParent(arguments);
24171
24172         var me = this,
24173             html = me.html,
24174             argsRe = me.argsRe,
24175             nameRe = me.nameRe,
24176             ifRe = me.ifRe,
24177             execRe = me.execRe,
24178             id = 0,
24179             tpls = [],
24180             VALUES = 'values',
24181             PARENT = 'parent',
24182             XINDEX = 'xindex',
24183             XCOUNT = 'xcount',
24184             RETURN = 'return ',
24185             WITHVALUES = 'with(values){ ',
24186             m, matchName, matchIf, matchExec, exp, fn, exec, name, i;
24187
24188         html = ['<tpl>', html, '</tpl>'].join('');
24189
24190         while ((m = html.match(argsRe))) {
24191             exp = null;
24192             fn = null;
24193             exec = null;
24194             matchName = m[0].match(nameRe);
24195             matchIf = m[0].match(ifRe);
24196             matchExec = m[0].match(execRe);
24197
24198             exp = matchIf ? matchIf[1] : null;
24199             if (exp) {
24200                 fn = Ext.functionFactory(VALUES, PARENT, XINDEX, XCOUNT, WITHVALUES + 'try{' + RETURN + Ext.String.htmlDecode(exp) + ';}catch(e){return;}}');
24201             }
24202
24203             exp = matchExec ? matchExec[1] : null;
24204             if (exp) {
24205                 exec = Ext.functionFactory(VALUES, PARENT, XINDEX, XCOUNT, WITHVALUES + Ext.String.htmlDecode(exp) + ';}');
24206             }
24207
24208             name = matchName ? matchName[1] : null;
24209             if (name) {
24210                 if (name === '.') {
24211                     name = VALUES;
24212                 } else if (name === '..') {
24213                     name = PARENT;
24214                 }
24215                 name = Ext.functionFactory(VALUES, PARENT, 'try{' + WITHVALUES + RETURN + name + ';}}catch(e){return;}');
24216             }
24217
24218             tpls.push({
24219                 id: id,
24220                 target: name,
24221                 exec: exec,
24222                 test: fn,
24223                 body: m[1] || ''
24224             });
24225
24226             html = html.replace(m[0], '{xtpl' + id + '}');
24227             id = id + 1;
24228         }
24229
24230         for (i = tpls.length - 1; i >= 0; --i) {
24231             me.compileTpl(tpls[i]);
24232         }
24233         me.master = tpls[tpls.length - 1];
24234         me.tpls = tpls;
24235     },
24236
24237     // @private
24238     applySubTemplate: function(id, values, parent, xindex, xcount) {
24239         var me = this, t = me.tpls[id];
24240         return t.compiled.call(me, values, parent, xindex, xcount);
24241     },
24242     /**
24243      * @cfg {RegExp} codeRe The regular expression used to match code variables (default: matches <tt>{[expression]}</tt>).
24244      */
24245     codeRe: /\{\[((?:\\\]|.|\n)*?)\]\}/g,
24246
24247     re: /\{([\w-\.\#]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?(\s?[\+\-\*\/]\s?[\d\.\+\-\*\/\(\)]+)?\}/g,
24248
24249     // @private
24250     compileTpl: function(tpl) {
24251         var fm = Ext.util.Format,
24252             me = this,
24253             useFormat = me.disableFormats !== true,
24254             body, bodyReturn, evaluatedFn;
24255
24256         function fn(m, name, format, args, math) {
24257             var v;
24258             // name is what is inside the {}
24259             // Name begins with xtpl, use a Sub Template
24260             if (name.substr(0, 4) == 'xtpl') {
24261                 return "',this.applySubTemplate(" + name.substr(4) + ", values, parent, xindex, xcount),'";
24262             }
24263             // name = "." - Just use the values object.
24264             if (name == '.') {
24265                 // filter to not include arrays/objects/nulls
24266                 v = 'Ext.Array.indexOf(["string", "number", "boolean"], typeof values) > -1 || Ext.isDate(values) ? values : ""';
24267             }
24268
24269             // name = "#" - Use the xindex
24270             else if (name == '#') {
24271                 v = 'xindex';
24272             }
24273             else if (name.substr(0, 7) == "parent.") {
24274                 v = name;
24275             }
24276             // name has a . in it - Use object literal notation, starting from values
24277             else if (name.indexOf('.') != -1) {
24278                 v = "values." + name;
24279             }
24280
24281             // name is a property of values
24282             else {
24283                 v = "values['" + name + "']";
24284             }
24285             if (math) {
24286                 v = '(' + v + math + ')';
24287             }
24288             if (format && useFormat) {
24289                 args = args ? ',' + args : "";
24290                 if (format.substr(0, 5) != "this.") {
24291                     format = "fm." + format + '(';
24292                 }
24293                 else {
24294                     format = 'this.' + format.substr(5) + '(';
24295                 }
24296             }
24297             else {
24298                 args = '';
24299                 format = "(" + v + " === undefined ? '' : ";
24300             }
24301             return "'," + format + v + args + "),'";
24302         }
24303
24304         function codeFn(m, code) {
24305             // Single quotes get escaped when the template is compiled, however we want to undo this when running code.
24306             return "',(" + code.replace(me.compileARe, "'") + "),'";
24307         }
24308
24309         bodyReturn = tpl.body.replace(me.compileBRe, '\\n').replace(me.compileCRe, "\\'").replace(me.re, fn).replace(me.codeRe, codeFn);
24310         body = "evaluatedFn = function(values, parent, xindex, xcount){return ['" + bodyReturn + "'].join('');};";
24311         eval(body);
24312
24313         tpl.compiled = function(values, parent, xindex, xcount) {
24314             var vs,
24315                 length,
24316                 buffer,
24317                 i;
24318
24319             if (tpl.test && !tpl.test.call(me, values, parent, xindex, xcount)) {
24320                 return '';
24321             }
24322
24323             vs = tpl.target ? tpl.target.call(me, values, parent) : values;
24324             if (!vs) {
24325                return '';
24326             }
24327
24328             parent = tpl.target ? values : parent;
24329             if (tpl.target && Ext.isArray(vs)) {
24330                 buffer = [];
24331                 length = vs.length;
24332                 if (tpl.exec) {
24333                     for (i = 0; i < length; i++) {
24334                         buffer[buffer.length] = evaluatedFn.call(me, vs[i], parent, i + 1, length);
24335                         tpl.exec.call(me, vs[i], parent, i + 1, length);
24336                     }
24337                 } else {
24338                     for (i = 0; i < length; i++) {
24339                         buffer[buffer.length] = evaluatedFn.call(me, vs[i], parent, i + 1, length);
24340                     }
24341                 }
24342                 return buffer.join('');
24343             }
24344
24345             if (tpl.exec) {
24346                 tpl.exec.call(me, vs, parent, xindex, xcount);
24347             }
24348             return evaluatedFn.call(me, vs, parent, xindex, xcount);
24349         };
24350
24351         return this;
24352     },
24353
24354     /**
24355      * Returns an HTML fragment of this template with the specified values applied.
24356      * @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'})
24357      * @return {String} The HTML fragment
24358      */
24359     applyTemplate: function(values) {
24360         return this.master.compiled.call(this, values, {}, 1, 1);
24361     },
24362
24363     /**
24364      * Compile the template to a function for optimized performance.  Recommended if the template will be used frequently.
24365      * @return {Function} The compiled function
24366      */
24367     compile: function() {
24368         return this;
24369     }
24370 }, function() {
24371     /**
24372      * Alias for {@link #applyTemplate}
24373      * Returns an HTML fragment of this template with the specified values applied.
24374      * @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'})
24375      * @return {String} The HTML fragment
24376      * @member Ext.XTemplate
24377      * @method apply
24378      */
24379     this.createAlias('apply', 'applyTemplate');
24380 });
24381
24382 /**
24383  * @class Ext.util.AbstractMixedCollection
24384  */
24385 Ext.define('Ext.util.AbstractMixedCollection', {
24386     requires: ['Ext.util.Filter'],
24387     
24388     mixins: {
24389         observable: 'Ext.util.Observable'
24390     },
24391
24392     constructor: function(allowFunctions, keyFn) {
24393         var me = this;
24394
24395         me.items = [];
24396         me.map = {};
24397         me.keys = [];
24398         me.length = 0;
24399
24400         me.addEvents(
24401             /**
24402              * @event clear
24403              * Fires when the collection is cleared.
24404              */
24405             'clear',
24406
24407             /**
24408              * @event add
24409              * Fires when an item is added to the collection.
24410              * @param {Number} index The index at which the item was added.
24411              * @param {Object} o The item added.
24412              * @param {String} key The key associated with the added item.
24413              */
24414             'add',
24415
24416             /**
24417              * @event replace
24418              * Fires when an item is replaced in the collection.
24419              * @param {String} key he key associated with the new added.
24420              * @param {Object} old The item being replaced.
24421              * @param {Object} new The new item.
24422              */
24423             'replace',
24424
24425             /**
24426              * @event remove
24427              * Fires when an item is removed from the collection.
24428              * @param {Object} o The item being removed.
24429              * @param {String} key (optional) The key associated with the removed item.
24430              */
24431             'remove'
24432         );
24433
24434         me.allowFunctions = allowFunctions === true;
24435
24436         if (keyFn) {
24437             me.getKey = keyFn;
24438         }
24439
24440         me.mixins.observable.constructor.call(me);
24441     },
24442     
24443     /**
24444      * @cfg {Boolean} allowFunctions Specify <tt>true</tt> if the {@link #addAll}
24445      * function should add function references to the collection. Defaults to
24446      * <tt>false</tt>.
24447      */
24448     allowFunctions : false,
24449
24450     /**
24451      * Adds an item to the collection. Fires the {@link #add} event when complete.
24452      * @param {String} key <p>The key to associate with the item, or the new item.</p>
24453      * <p>If a {@link #getKey} implementation was specified for this MixedCollection,
24454      * or if the key of the stored items is in a property called <tt><b>id</b></tt>,
24455      * the MixedCollection will be able to <i>derive</i> the key for the new item.
24456      * In this case just pass the new item in this parameter.</p>
24457      * @param {Object} o The item to add.
24458      * @return {Object} The item added.
24459      */
24460     add : function(key, obj){
24461         var me = this,
24462             myObj = obj,
24463             myKey = key,
24464             old;
24465
24466         if (arguments.length == 1) {
24467             myObj = myKey;
24468             myKey = me.getKey(myObj);
24469         }
24470         if (typeof myKey != 'undefined' && myKey !== null) {
24471             old = me.map[myKey];
24472             if (typeof old != 'undefined') {
24473                 return me.replace(myKey, myObj);
24474             }
24475             me.map[myKey] = myObj;
24476         }
24477         me.length++;
24478         me.items.push(myObj);
24479         me.keys.push(myKey);
24480         me.fireEvent('add', me.length - 1, myObj, myKey);
24481         return myObj;
24482     },
24483
24484     /**
24485       * MixedCollection has a generic way to fetch keys if you implement getKey.  The default implementation
24486       * simply returns <b><code>item.id</code></b> but you can provide your own implementation
24487       * to return a different value as in the following examples:<pre><code>
24488 // normal way
24489 var mc = new Ext.util.MixedCollection();
24490 mc.add(someEl.dom.id, someEl);
24491 mc.add(otherEl.dom.id, otherEl);
24492 //and so on
24493
24494 // using getKey
24495 var mc = new Ext.util.MixedCollection();
24496 mc.getKey = function(el){
24497    return el.dom.id;
24498 };
24499 mc.add(someEl);
24500 mc.add(otherEl);
24501
24502 // or via the constructor
24503 var mc = new Ext.util.MixedCollection(false, function(el){
24504    return el.dom.id;
24505 });
24506 mc.add(someEl);
24507 mc.add(otherEl);
24508      * </code></pre>
24509      * @param {Object} item The item for which to find the key.
24510      * @return {Object} The key for the passed item.
24511      */
24512     getKey : function(o){
24513          return o.id;
24514     },
24515
24516     /**
24517      * Replaces an item in the collection. Fires the {@link #replace} event when complete.
24518      * @param {String} key <p>The key associated with the item to replace, or the replacement item.</p>
24519      * <p>If you supplied a {@link #getKey} implementation for this MixedCollection, or if the key
24520      * of your stored items is in a property called <tt><b>id</b></tt>, then the MixedCollection
24521      * will be able to <i>derive</i> the key of the replacement item. If you want to replace an item
24522      * with one having the same key value, then just pass the replacement item in this parameter.</p>
24523      * @param o {Object} o (optional) If the first parameter passed was a key, the item to associate
24524      * with that key.
24525      * @return {Object}  The new item.
24526      */
24527     replace : function(key, o){
24528         var me = this,
24529             old,
24530             index;
24531
24532         if (arguments.length == 1) {
24533             o = arguments[0];
24534             key = me.getKey(o);
24535         }
24536         old = me.map[key];
24537         if (typeof key == 'undefined' || key === null || typeof old == 'undefined') {
24538              return me.add(key, o);
24539         }
24540         index = me.indexOfKey(key);
24541         me.items[index] = o;
24542         me.map[key] = o;
24543         me.fireEvent('replace', key, old, o);
24544         return o;
24545     },
24546
24547     /**
24548      * Adds all elements of an Array or an Object to the collection.
24549      * @param {Object/Array} objs An Object containing properties which will be added
24550      * to the collection, or an Array of values, each of which are added to the collection.
24551      * Functions references will be added to the collection if <code>{@link #allowFunctions}</code>
24552      * has been set to <tt>true</tt>.
24553      */
24554     addAll : function(objs){
24555         var me = this,
24556             i = 0,
24557             args,
24558             len,
24559             key;
24560
24561         if (arguments.length > 1 || Ext.isArray(objs)) {
24562             args = arguments.length > 1 ? arguments : objs;
24563             for (len = args.length; i < len; i++) {
24564                 me.add(args[i]);
24565             }
24566         } else {
24567             for (key in objs) {
24568                 if (objs.hasOwnProperty(key)) {
24569                     if (me.allowFunctions || typeof objs[key] != 'function') {
24570                         me.add(key, objs[key]);
24571                     }
24572                 }
24573             }
24574         }
24575     },
24576
24577     /**
24578      * Executes the specified function once for every item in the collection, passing the following arguments:
24579      * <div class="mdetail-params"><ul>
24580      * <li><b>item</b> : Mixed<p class="sub-desc">The collection item</p></li>
24581      * <li><b>index</b> : Number<p class="sub-desc">The item's index</p></li>
24582      * <li><b>length</b> : Number<p class="sub-desc">The total number of items in the collection</p></li>
24583      * </ul></div>
24584      * The function should return a boolean value. Returning false from the function will stop the iteration.
24585      * @param {Function} fn The function to execute for each item.
24586      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current item in the iteration.
24587      */
24588     each : function(fn, scope){
24589         var items = [].concat(this.items), // each safe for removal
24590             i = 0,
24591             len = items.length,
24592             item;
24593
24594         for (; i < len; i++) {
24595             item = items[i];
24596             if (fn.call(scope || item, item, i, len) === false) {
24597                 break;
24598             }
24599         }
24600     },
24601
24602     /**
24603      * Executes the specified function once for every key in the collection, passing each
24604      * key, and its associated item as the first two parameters.
24605      * @param {Function} fn The function to execute for each item.
24606      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the browser window.
24607      */
24608     eachKey : function(fn, scope){
24609         var keys = this.keys,
24610             items = this.items,
24611             i = 0,
24612             len = keys.length;
24613
24614         for (; i < len; i++) {
24615             fn.call(scope || window, keys[i], items[i], i, len);
24616         }
24617     },
24618
24619     /**
24620      * Returns the first item in the collection which elicits a true return value from the
24621      * passed selection function.
24622      * @param {Function} fn The selection function to execute for each item.
24623      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the browser window.
24624      * @return {Object} The first item in the collection which returned true from the selection function.
24625      */
24626     findBy : function(fn, scope) {
24627         var keys = this.keys,
24628             items = this.items,
24629             i = 0,
24630             len = items.length;
24631
24632         for (; i < len; i++) {
24633             if (fn.call(scope || window, items[i], keys[i])) {
24634                 return items[i];
24635             }
24636         }
24637         return null;
24638     },
24639
24640     find : function() {
24641         if (Ext.isDefined(Ext.global.console)) {
24642             Ext.global.console.warn('Ext.util.MixedCollection: find has been deprecated. Use findBy instead.');
24643         }
24644         return this.findBy.apply(this, arguments);
24645     },
24646
24647     /**
24648      * Inserts an item at the specified index in the collection. Fires the {@link #add} event when complete.
24649      * @param {Number} index The index to insert the item at.
24650      * @param {String} key The key to associate with the new item, or the item itself.
24651      * @param {Object} o (optional) If the second parameter was a key, the new item.
24652      * @return {Object} The item inserted.
24653      */
24654     insert : function(index, key, obj){
24655         var me = this,
24656             myKey = key,
24657             myObj = obj;
24658
24659         if (arguments.length == 2) {
24660             myObj = myKey;
24661             myKey = me.getKey(myObj);
24662         }
24663         if (me.containsKey(myKey)) {
24664             me.suspendEvents();
24665             me.removeAtKey(myKey);
24666             me.resumeEvents();
24667         }
24668         if (index >= me.length) {
24669             return me.add(myKey, myObj);
24670         }
24671         me.length++;
24672         me.items.splice(index, 0, myObj);
24673         if (typeof myKey != 'undefined' && myKey !== null) {
24674             me.map[myKey] = myObj;
24675         }
24676         me.keys.splice(index, 0, myKey);
24677         me.fireEvent('add', index, myObj, myKey);
24678         return myObj;
24679     },
24680
24681     /**
24682      * Remove an item from the collection.
24683      * @param {Object} o The item to remove.
24684      * @return {Object} The item removed or false if no item was removed.
24685      */
24686     remove : function(o){
24687         return this.removeAt(this.indexOf(o));
24688     },
24689
24690     /**
24691      * Remove all items in the passed array from the collection.
24692      * @param {Array} items An array of items to be removed.
24693      * @return {Ext.util.MixedCollection} this object
24694      */
24695     removeAll : function(items){
24696         Ext.each(items || [], function(item) {
24697             this.remove(item);
24698         }, this);
24699
24700         return this;
24701     },
24702
24703     /**
24704      * Remove an item from a specified index in the collection. Fires the {@link #remove} event when complete.
24705      * @param {Number} index The index within the collection of the item to remove.
24706      * @return {Object} The item removed or false if no item was removed.
24707      */
24708     removeAt : function(index){
24709         var me = this,
24710             o,
24711             key;
24712
24713         if (index < me.length && index >= 0) {
24714             me.length--;
24715             o = me.items[index];
24716             me.items.splice(index, 1);
24717             key = me.keys[index];
24718             if (typeof key != 'undefined') {
24719                 delete me.map[key];
24720             }
24721             me.keys.splice(index, 1);
24722             me.fireEvent('remove', o, key);
24723             return o;
24724         }
24725         return false;
24726     },
24727
24728     /**
24729      * Removed an item associated with the passed key fom the collection.
24730      * @param {String} key The key of the item to remove.
24731      * @return {Object} The item removed or false if no item was removed.
24732      */
24733     removeAtKey : function(key){
24734         return this.removeAt(this.indexOfKey(key));
24735     },
24736
24737     /**
24738      * Returns the number of items in the collection.
24739      * @return {Number} the number of items in the collection.
24740      */
24741     getCount : function(){
24742         return this.length;
24743     },
24744
24745     /**
24746      * Returns index within the collection of the passed Object.
24747      * @param {Object} o The item to find the index of.
24748      * @return {Number} index of the item. Returns -1 if not found.
24749      */
24750     indexOf : function(o){
24751         return Ext.Array.indexOf(this.items, o);
24752     },
24753
24754     /**
24755      * Returns index within the collection of the passed key.
24756      * @param {String} key The key to find the index of.
24757      * @return {Number} index of the key.
24758      */
24759     indexOfKey : function(key){
24760         return Ext.Array.indexOf(this.keys, key);
24761     },
24762
24763     /**
24764      * Returns the item associated with the passed key OR index.
24765      * Key has priority over index.  This is the equivalent
24766      * of calling {@link #key} first, then if nothing matched calling {@link #getAt}.
24767      * @param {String/Number} key The key or index of the item.
24768      * @return {Object} If the item is found, returns the item.  If the item was not found, returns <tt>undefined</tt>.
24769      * If an item was found, but is a Class, returns <tt>null</tt>.
24770      */
24771     get : function(key) {
24772         var me = this,
24773             mk = me.map[key],
24774             item = mk !== undefined ? mk : (typeof key == 'number') ? me.items[key] : undefined;
24775         return typeof item != 'function' || me.allowFunctions ? item : null; // for prototype!
24776     },
24777
24778     /**
24779      * Returns the item at the specified index.
24780      * @param {Number} index The index of the item.
24781      * @return {Object} The item at the specified index.
24782      */
24783     getAt : function(index) {
24784         return this.items[index];
24785     },
24786
24787     /**
24788      * Returns the item associated with the passed key.
24789      * @param {String/Number} key The key of the item.
24790      * @return {Object} The item associated with the passed key.
24791      */
24792     getByKey : function(key) {
24793         return this.map[key];
24794     },
24795
24796     /**
24797      * Returns true if the collection contains the passed Object as an item.
24798      * @param {Object} o  The Object to look for in the collection.
24799      * @return {Boolean} True if the collection contains the Object as an item.
24800      */
24801     contains : function(o){
24802         return Ext.Array.contains(this.items, o);
24803     },
24804
24805     /**
24806      * Returns true if the collection contains the passed Object as a key.
24807      * @param {String} key The key to look for in the collection.
24808      * @return {Boolean} True if the collection contains the Object as a key.
24809      */
24810     containsKey : function(key){
24811         return typeof this.map[key] != 'undefined';
24812     },
24813
24814     /**
24815      * Removes all items from the collection.  Fires the {@link #clear} event when complete.
24816      */
24817     clear : function(){
24818         var me = this;
24819
24820         me.length = 0;
24821         me.items = [];
24822         me.keys = [];
24823         me.map = {};
24824         me.fireEvent('clear');
24825     },
24826
24827     /**
24828      * Returns the first item in the collection.
24829      * @return {Object} the first item in the collection..
24830      */
24831     first : function() {
24832         return this.items[0];
24833     },
24834
24835     /**
24836      * Returns the last item in the collection.
24837      * @return {Object} the last item in the collection..
24838      */
24839     last : function() {
24840         return this.items[this.length - 1];
24841     },
24842
24843     /**
24844      * Collects all of the values of the given property and returns their sum
24845      * @param {String} property The property to sum by
24846      * @param {String} root Optional 'root' property to extract the first argument from. This is used mainly when
24847      * summing fields in records, where the fields are all stored inside the 'data' object
24848      * @param {Number} start (optional) The record index to start at (defaults to <tt>0</tt>)
24849      * @param {Number} end (optional) The record index to end at (defaults to <tt>-1</tt>)
24850      * @return {Number} The total
24851      */
24852     sum: function(property, root, start, end) {
24853         var values = this.extractValues(property, root),
24854             length = values.length,
24855             sum    = 0,
24856             i;
24857
24858         start = start || 0;
24859         end   = (end || end === 0) ? end : length - 1;
24860
24861         for (i = start; i <= end; i++) {
24862             sum += values[i];
24863         }
24864
24865         return sum;
24866     },
24867
24868     /**
24869      * Collects unique values of a particular property in this MixedCollection
24870      * @param {String} property The property to collect on
24871      * @param {String} root Optional 'root' property to extract the first argument from. This is used mainly when
24872      * summing fields in records, where the fields are all stored inside the 'data' object
24873      * @param {Boolean} allowBlank (optional) Pass true to allow null, undefined or empty string values
24874      * @return {Array} The unique values
24875      */
24876     collect: function(property, root, allowNull) {
24877         var values = this.extractValues(property, root),
24878             length = values.length,
24879             hits   = {},
24880             unique = [],
24881             value, strValue, i;
24882
24883         for (i = 0; i < length; i++) {
24884             value = values[i];
24885             strValue = String(value);
24886
24887             if ((allowNull || !Ext.isEmpty(value)) && !hits[strValue]) {
24888                 hits[strValue] = true;
24889                 unique.push(value);
24890             }
24891         }
24892
24893         return unique;
24894     },
24895
24896     /**
24897      * @private
24898      * Extracts all of the given property values from the items in the MC. Mainly used as a supporting method for
24899      * functions like sum and collect.
24900      * @param {String} property The property to extract
24901      * @param {String} root Optional 'root' property to extract the first argument from. This is used mainly when
24902      * extracting field data from Model instances, where the fields are stored inside the 'data' object
24903      * @return {Array} The extracted values
24904      */
24905     extractValues: function(property, root) {
24906         var values = this.items;
24907
24908         if (root) {
24909             values = Ext.Array.pluck(values, root);
24910         }
24911
24912         return Ext.Array.pluck(values, property);
24913     },
24914
24915     /**
24916      * Returns a range of items in this collection
24917      * @param {Number} startIndex (optional) The starting index. Defaults to 0.
24918      * @param {Number} endIndex (optional) The ending index. Defaults to the last item.
24919      * @return {Array} An array of items
24920      */
24921     getRange : function(start, end){
24922         var me = this,
24923             items = me.items,
24924             range = [],
24925             i;
24926
24927         if (items.length < 1) {
24928             return range;
24929         }
24930
24931         start = start || 0;
24932         end = Math.min(typeof end == 'undefined' ? me.length - 1 : end, me.length - 1);
24933         if (start <= end) {
24934             for (i = start; i <= end; i++) {
24935                 range[range.length] = items[i];
24936             }
24937         } else {
24938             for (i = start; i >= end; i--) {
24939                 range[range.length] = items[i];
24940             }
24941         }
24942         return range;
24943     },
24944
24945     /**
24946      * <p>Filters the objects in this collection by a set of {@link Ext.util.Filter Filter}s, or by a single
24947      * property/value pair with optional parameters for substring matching and case sensitivity. See
24948      * {@link Ext.util.Filter Filter} for an example of using Filter objects (preferred). Alternatively,
24949      * MixedCollection can be easily filtered by property like this:</p>
24950 <pre><code>
24951 //create a simple store with a few people defined
24952 var people = new Ext.util.MixedCollection();
24953 people.addAll([
24954     {id: 1, age: 25, name: 'Ed'},
24955     {id: 2, age: 24, name: 'Tommy'},
24956     {id: 3, age: 24, name: 'Arne'},
24957     {id: 4, age: 26, name: 'Aaron'}
24958 ]);
24959
24960 //a new MixedCollection containing only the items where age == 24
24961 var middleAged = people.filter('age', 24);
24962 </code></pre>
24963      *
24964      *
24965      * @param {Array/String} property A property on your objects, or an array of {@link Ext.util.Filter Filter} objects
24966      * @param {String/RegExp} value Either string that the property values
24967      * should start with or a RegExp to test against the property
24968      * @param {Boolean} anyMatch (optional) True to match any part of the string, not just the beginning
24969      * @param {Boolean} caseSensitive (optional) True for case sensitive comparison (defaults to False).
24970      * @return {MixedCollection} The new filtered collection
24971      */
24972     filter : function(property, value, anyMatch, caseSensitive) {
24973         var filters = [],
24974             filterFn;
24975
24976         //support for the simple case of filtering by property/value
24977         if (Ext.isString(property)) {
24978             filters.push(Ext.create('Ext.util.Filter', {
24979                 property     : property,
24980                 value        : value,
24981                 anyMatch     : anyMatch,
24982                 caseSensitive: caseSensitive
24983             }));
24984         } else if (Ext.isArray(property) || property instanceof Ext.util.Filter) {
24985             filters = filters.concat(property);
24986         }
24987
24988         //at this point we have an array of zero or more Ext.util.Filter objects to filter with,
24989         //so here we construct a function that combines these filters by ANDing them together
24990         filterFn = function(record) {
24991             var isMatch = true,
24992                 length = filters.length,
24993                 i;
24994
24995             for (i = 0; i < length; i++) {
24996                 var filter = filters[i],
24997                     fn     = filter.filterFn,
24998                     scope  = filter.scope;
24999
25000                 isMatch = isMatch && fn.call(scope, record);
25001             }
25002
25003             return isMatch;
25004         };
25005
25006         return this.filterBy(filterFn);
25007     },
25008
25009     /**
25010      * Filter by a function. Returns a <i>new</i> collection that has been filtered.
25011      * The passed function will be called with each object in the collection.
25012      * If the function returns true, the value is included otherwise it is filtered.
25013      * @param {Function} fn The function to be called, it will receive the args o (the object), k (the key)
25014      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this MixedCollection.
25015      * @return {MixedCollection} The new filtered collection
25016      */
25017     filterBy : function(fn, scope) {
25018         var me = this,
25019             newMC  = new this.self(),
25020             keys   = me.keys,
25021             items  = me.items,
25022             length = items.length,
25023             i;
25024
25025         newMC.getKey = me.getKey;
25026
25027         for (i = 0; i < length; i++) {
25028             if (fn.call(scope || me, items[i], keys[i])) {
25029                 newMC.add(keys[i], items[i]);
25030             }
25031         }
25032
25033         return newMC;
25034     },
25035
25036     /**
25037      * Finds the index of the first matching object in this collection by a specific property/value.
25038      * @param {String} property The name of a property on your objects.
25039      * @param {String/RegExp} value A string that the property values
25040      * should start with or a RegExp to test against the property.
25041      * @param {Number} start (optional) The index to start searching at (defaults to 0).
25042      * @param {Boolean} anyMatch (optional) True to match any part of the string, not just the beginning.
25043      * @param {Boolean} caseSensitive (optional) True for case sensitive comparison.
25044      * @return {Number} The matched index or -1
25045      */
25046     findIndex : function(property, value, start, anyMatch, caseSensitive){
25047         if(Ext.isEmpty(value, false)){
25048             return -1;
25049         }
25050         value = this.createValueMatcher(value, anyMatch, caseSensitive);
25051         return this.findIndexBy(function(o){
25052             return o && value.test(o[property]);
25053         }, null, start);
25054     },
25055
25056     /**
25057      * Find the index of the first matching object in this collection by a function.
25058      * If the function returns <i>true</i> it is considered a match.
25059      * @param {Function} fn The function to be called, it will receive the args o (the object), k (the key).
25060      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this MixedCollection.
25061      * @param {Number} start (optional) The index to start searching at (defaults to 0).
25062      * @return {Number} The matched index or -1
25063      */
25064     findIndexBy : function(fn, scope, start){
25065         var me = this,
25066             keys = me.keys,
25067             items = me.items,
25068             i = start || 0,
25069             len = items.length;
25070
25071         for (; i < len; i++) {
25072             if (fn.call(scope || me, items[i], keys[i])) {
25073                 return i;
25074             }
25075         }
25076         return -1;
25077     },
25078
25079     /**
25080      * Returns a regular expression based on the given value and matching options. This is used internally for finding and filtering,
25081      * and by Ext.data.Store#filter
25082      * @private
25083      * @param {String} value The value to create the regex for. This is escaped using Ext.escapeRe
25084      * @param {Boolean} anyMatch True to allow any match - no regex start/end line anchors will be added. Defaults to false
25085      * @param {Boolean} caseSensitive True to make the regex case sensitive (adds 'i' switch to regex). Defaults to false.
25086      * @param {Boolean} exactMatch True to force exact match (^ and $ characters added to the regex). Defaults to false. Ignored if anyMatch is true.
25087      */
25088     createValueMatcher : function(value, anyMatch, caseSensitive, exactMatch) {
25089         if (!value.exec) { // not a regex
25090             var er = Ext.String.escapeRegex;
25091             value = String(value);
25092
25093             if (anyMatch === true) {
25094                 value = er(value);
25095             } else {
25096                 value = '^' + er(value);
25097                 if (exactMatch === true) {
25098                     value += '$';
25099                 }
25100             }
25101             value = new RegExp(value, caseSensitive ? '' : 'i');
25102         }
25103         return value;
25104     },
25105
25106     /**
25107      * Creates a shallow copy of this collection
25108      * @return {MixedCollection}
25109      */
25110     clone : function() {
25111         var me = this,
25112             copy = new this.self(),
25113             keys = me.keys,
25114             items = me.items,
25115             i = 0,
25116             len = items.length;
25117
25118         for(; i < len; i++){
25119             copy.add(keys[i], items[i]);
25120         }
25121         copy.getKey = me.getKey;
25122         return copy;
25123     }
25124 });
25125
25126 /**
25127  * @class Ext.util.Sortable
25128
25129 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}.
25130
25131 **NOTE**: This mixin is mainly for internal library use and most users should not need to use it directly. It
25132 is more likely you will want to use one of the component classes that import this mixin, such as
25133 {@link Ext.data.Store} or {@link Ext.data.TreeStore}.
25134  * @markdown
25135  * @docauthor Tommy Maintz <tommy@sencha.com>
25136  */
25137 Ext.define("Ext.util.Sortable", {
25138     /**
25139      * @property isSortable
25140      * @type Boolean
25141      * Flag denoting that this object is sortable. Always true.
25142      */
25143     isSortable: true,
25144     
25145     /**
25146      * The default sort direction to use if one is not specified (defaults to "ASC")
25147      * @property defaultSortDirection
25148      * @type String
25149      */
25150     defaultSortDirection: "ASC",
25151     
25152     requires: [
25153         'Ext.util.Sorter'
25154     ],
25155
25156     /**
25157      * The property in each item that contains the data to sort. (defaults to null)
25158      * @type String
25159      */    
25160     sortRoot: null,
25161     
25162     /**
25163      * Performs initialization of this mixin. Component classes using this mixin should call this method
25164      * during their own initialization.
25165      */
25166     initSortable: function() {
25167         var me = this,
25168             sorters = me.sorters;
25169         
25170         /**
25171          * The collection of {@link Ext.util.Sorter Sorters} currently applied to this Store
25172          * @property sorters
25173          * @type Ext.util.MixedCollection
25174          */
25175         me.sorters = Ext.create('Ext.util.AbstractMixedCollection', false, function(item) {
25176             return item.id || item.property;
25177         });
25178         
25179         if (sorters) {
25180             me.sorters.addAll(me.decodeSorters(sorters));
25181         }
25182     },
25183
25184     /**
25185      * <p>Sorts the data in the Store by one or more of its properties. Example usage:</p>
25186 <pre><code>
25187 //sort by a single field
25188 myStore.sort('myField', 'DESC');
25189
25190 //sorting by multiple fields
25191 myStore.sort([
25192     {
25193         property : 'age',
25194         direction: 'ASC'
25195     },
25196     {
25197         property : 'name',
25198         direction: 'DESC'
25199     }
25200 ]);
25201 </code></pre>
25202      * <p>Internally, Store converts the passed arguments into an array of {@link Ext.util.Sorter} instances, and delegates the actual
25203      * sorting to its internal {@link Ext.util.MixedCollection}.</p>
25204      * <p>When passing a single string argument to sort, Store maintains a ASC/DESC toggler per field, so this code:</p>
25205 <pre><code>
25206 store.sort('myField');
25207 store.sort('myField');
25208      </code></pre>
25209      * <p>Is equivalent to this code, because Store handles the toggling automatically:</p>
25210 <pre><code>
25211 store.sort('myField', 'ASC');
25212 store.sort('myField', 'DESC');
25213 </code></pre>
25214      * @param {String|Array} sorters Either a string name of one of the fields in this Store's configured {@link Ext.data.Model Model},
25215      * or an Array of sorter configurations.
25216      * @param {String} direction The overall direction to sort the data by. Defaults to "ASC".
25217      */
25218     sort: function(sorters, direction, where, doSort) {
25219         var me = this,
25220             sorter, sorterFn,
25221             newSorters;
25222         
25223         if (Ext.isArray(sorters)) {
25224             doSort = where;
25225             where = direction;
25226             newSorters = sorters;
25227         }
25228         else if (Ext.isObject(sorters)) {
25229             doSort = where;
25230             where = direction;
25231             newSorters = [sorters];
25232         }
25233         else if (Ext.isString(sorters)) {
25234             sorter = me.sorters.get(sorters);
25235
25236             if (!sorter) {
25237                 sorter = {
25238                     property : sorters,
25239                     direction: direction
25240                 };
25241                 newSorters = [sorter];
25242             }
25243             else if (direction === undefined) {
25244                 sorter.toggle();
25245             }
25246             else {
25247                 sorter.setDirection(direction);
25248             }
25249         }
25250         
25251         if (newSorters && newSorters.length) {
25252             newSorters = me.decodeSorters(newSorters);
25253             if (Ext.isString(where)) {
25254                 if (where === 'prepend') {
25255                     sorters = me.sorters.clone().items;
25256                     
25257                     me.sorters.clear();
25258                     me.sorters.addAll(newSorters);
25259                     me.sorters.addAll(sorters);
25260                 }
25261                 else {
25262                     me.sorters.addAll(newSorters);
25263                 }
25264             }
25265             else {
25266                 me.sorters.clear();
25267                 me.sorters.addAll(newSorters);
25268             }
25269             
25270             if (doSort !== false) {
25271                 me.onBeforeSort(newSorters);
25272             }
25273         }
25274         
25275         if (doSort !== false) {
25276             sorters = me.sorters.items;
25277             if (sorters.length) {
25278                 //construct an amalgamated sorter function which combines all of the Sorters passed
25279                 sorterFn = function(r1, r2) {
25280                     var result = sorters[0].sort(r1, r2),
25281                         length = sorters.length,
25282                         i;
25283
25284                         //if we have more than one sorter, OR any additional sorter functions together
25285                         for (i = 1; i < length; i++) {
25286                             result = result || sorters[i].sort.call(this, r1, r2);
25287                         }
25288
25289                     return result;
25290                 };
25291
25292                 me.doSort(sorterFn);                
25293             }
25294         }
25295         
25296         return sorters;
25297     },
25298     
25299     onBeforeSort: Ext.emptyFn,
25300         
25301     /**
25302      * @private
25303      * Normalizes an array of sorter objects, ensuring that they are all Ext.util.Sorter instances
25304      * @param {Array} sorters The sorters array
25305      * @return {Array} Array of Ext.util.Sorter objects
25306      */
25307     decodeSorters: function(sorters) {
25308         if (!Ext.isArray(sorters)) {
25309             if (sorters === undefined) {
25310                 sorters = [];
25311             } else {
25312                 sorters = [sorters];
25313             }
25314         }
25315
25316         var length = sorters.length,
25317             Sorter = Ext.util.Sorter,
25318             fields = this.model ? this.model.prototype.fields : null,
25319             field,
25320             config, i;
25321
25322         for (i = 0; i < length; i++) {
25323             config = sorters[i];
25324
25325             if (!(config instanceof Sorter)) {
25326                 if (Ext.isString(config)) {
25327                     config = {
25328                         property: config
25329                     };
25330                 }
25331                 
25332                 Ext.applyIf(config, {
25333                     root     : this.sortRoot,
25334                     direction: "ASC"
25335                 });
25336
25337                 //support for 3.x style sorters where a function can be defined as 'fn'
25338                 if (config.fn) {
25339                     config.sorterFn = config.fn;
25340                 }
25341
25342                 //support a function to be passed as a sorter definition
25343                 if (typeof config == 'function') {
25344                     config = {
25345                         sorterFn: config
25346                     };
25347                 }
25348
25349                 // ensure sortType gets pushed on if necessary
25350                 if (fields && !config.transform) {
25351                     field = fields.get(config.property);
25352                     config.transform = field ? field.sortType : undefined;
25353                 }
25354                 sorters[i] = Ext.create('Ext.util.Sorter', config);
25355             }
25356         }
25357
25358         return sorters;
25359     },
25360     
25361     getSorters: function() {
25362         return this.sorters.items;
25363     },
25364     
25365     /**
25366      * Returns an object describing the current sort state of this Store.
25367      * @return {Object} The sort state of the Store. An object with two properties:<ul>
25368      * <li><b>field</b> : String<p class="sub-desc">The name of the field by which the Records are sorted.</p></li>
25369      * <li><b>direction</b> : String<p class="sub-desc">The sort order, 'ASC' or 'DESC' (case-sensitive).</p></li>
25370      * </ul>
25371      * See <tt>{@link #sortInfo}</tt> for additional details.
25372      */
25373     getSortState : function() {
25374         return this.sortInfo;
25375     }
25376 });
25377 /**
25378  * @class Ext.util.MixedCollection
25379  * <p>
25380  * Represents a collection of a set of key and value pairs. Each key in the MixedCollection
25381  * must be unique, the same key cannot exist twice. This collection is ordered, items in the
25382  * collection can be accessed by index  or via the key. Newly added items are added to
25383  * the end of the collection. This class is similar to {@link Ext.util.HashMap} however it
25384  * is heavier and provides more functionality. Sample usage:
25385  * <pre><code>
25386 var coll = new Ext.util.MixedCollection();
25387 coll.add('key1', 'val1');
25388 coll.add('key2', 'val2');
25389 coll.add('key3', 'val3');
25390
25391 console.log(coll.get('key1')); // prints 'val1'
25392 console.log(coll.indexOfKey('key3')); // prints 2
25393  * </code></pre>
25394  *
25395  * <p>
25396  * The MixedCollection also has support for sorting and filtering of the values in the collection.
25397  * <pre><code>
25398 var coll = new Ext.util.MixedCollection();
25399 coll.add('key1', 100);
25400 coll.add('key2', -100);
25401 coll.add('key3', 17);
25402 coll.add('key4', 0);
25403 var biggerThanZero = coll.filterBy(function(value){
25404     return value > 0;
25405 });
25406 console.log(biggerThanZero.getCount()); // prints 2
25407  * </code></pre>
25408  * </p>
25409  *
25410  * @constructor
25411  * @param {Boolean} allowFunctions Specify <tt>true</tt> if the {@link #addAll}
25412  * function should add function references to the collection. Defaults to
25413  * <tt>false</tt>.
25414  * @param {Function} keyFn A function that can accept an item of the type(s) stored in this MixedCollection
25415  * and return the key value for that item.  This is used when available to look up the key on items that
25416  * were passed without an explicit key parameter to a MixedCollection method.  Passing this parameter is
25417  * equivalent to providing an implementation for the {@link #getKey} method.
25418  */
25419 Ext.define('Ext.util.MixedCollection', {
25420     extend: 'Ext.util.AbstractMixedCollection',
25421     mixins: {
25422         sortable: 'Ext.util.Sortable'
25423     },
25424
25425     constructor: function() {
25426         var me = this;
25427         me.callParent(arguments);
25428         me.addEvents('sort');
25429         me.mixins.sortable.initSortable.call(me);
25430     },
25431
25432     doSort: function(sorterFn) {
25433         this.sortBy(sorterFn);
25434     },
25435
25436     /**
25437      * @private
25438      * Performs the actual sorting based on a direction and a sorting function. Internally,
25439      * this creates a temporary array of all items in the MixedCollection, sorts it and then writes
25440      * the sorted array data back into this.items and this.keys
25441      * @param {String} property Property to sort by ('key', 'value', or 'index')
25442      * @param {String} dir (optional) Direction to sort 'ASC' or 'DESC'. Defaults to 'ASC'.
25443      * @param {Function} fn (optional) Comparison function that defines the sort order.
25444      * Defaults to sorting by numeric value.
25445      */
25446     _sort : function(property, dir, fn){
25447         var me = this,
25448             i, len,
25449             dsc   = String(dir).toUpperCase() == 'DESC' ? -1 : 1,
25450
25451             //this is a temporary array used to apply the sorting function
25452             c     = [],
25453             keys  = me.keys,
25454             items = me.items;
25455
25456         //default to a simple sorter function if one is not provided
25457         fn = fn || function(a, b) {
25458             return a - b;
25459         };
25460
25461         //copy all the items into a temporary array, which we will sort
25462         for(i = 0, len = items.length; i < len; i++){
25463             c[c.length] = {
25464                 key  : keys[i],
25465                 value: items[i],
25466                 index: i
25467             };
25468         }
25469
25470         //sort the temporary array
25471         Ext.Array.sort(c, function(a, b){
25472             var v = fn(a[property], b[property]) * dsc;
25473             if(v === 0){
25474                 v = (a.index < b.index ? -1 : 1);
25475             }
25476             return v;
25477         });
25478
25479         //copy the temporary array back into the main this.items and this.keys objects
25480         for(i = 0, len = c.length; i < len; i++){
25481             items[i] = c[i].value;
25482             keys[i]  = c[i].key;
25483         }
25484
25485         me.fireEvent('sort', me);
25486     },
25487
25488     /**
25489      * Sorts the collection by a single sorter function
25490      * @param {Function} sorterFn The function to sort by
25491      */
25492     sortBy: function(sorterFn) {
25493         var me     = this,
25494             items  = me.items,
25495             keys   = me.keys,
25496             length = items.length,
25497             temp   = [],
25498             i;
25499
25500         //first we create a copy of the items array so that we can sort it
25501         for (i = 0; i < length; i++) {
25502             temp[i] = {
25503                 key  : keys[i],
25504                 value: items[i],
25505                 index: i
25506             };
25507         }
25508
25509         Ext.Array.sort(temp, function(a, b) {
25510             var v = sorterFn(a.value, b.value);
25511             if (v === 0) {
25512                 v = (a.index < b.index ? -1 : 1);
25513             }
25514
25515             return v;
25516         });
25517
25518         //copy the temporary array back into the main this.items and this.keys objects
25519         for (i = 0; i < length; i++) {
25520             items[i] = temp[i].value;
25521             keys[i]  = temp[i].key;
25522         }
25523         
25524         me.fireEvent('sort', me, items, keys);
25525     },
25526
25527     /**
25528      * Reorders each of the items based on a mapping from old index to new index. Internally this
25529      * just translates into a sort. The 'sort' event is fired whenever reordering has occured.
25530      * @param {Object} mapping Mapping from old item index to new item index
25531      */
25532     reorder: function(mapping) {
25533         var me = this,
25534             items = me.items,
25535             index = 0,
25536             length = items.length,
25537             order = [],
25538             remaining = [],
25539             oldIndex;
25540
25541         me.suspendEvents();
25542
25543         //object of {oldPosition: newPosition} reversed to {newPosition: oldPosition}
25544         for (oldIndex in mapping) {
25545             order[mapping[oldIndex]] = items[oldIndex];
25546         }
25547
25548         for (index = 0; index < length; index++) {
25549             if (mapping[index] == undefined) {
25550                 remaining.push(items[index]);
25551             }
25552         }
25553
25554         for (index = 0; index < length; index++) {
25555             if (order[index] == undefined) {
25556                 order[index] = remaining.shift();
25557             }
25558         }
25559
25560         me.clear();
25561         me.addAll(order);
25562
25563         me.resumeEvents();
25564         me.fireEvent('sort', me);
25565     },
25566
25567     /**
25568      * Sorts this collection by <b>key</b>s.
25569      * @param {String} direction (optional) 'ASC' or 'DESC'. Defaults to 'ASC'.
25570      * @param {Function} fn (optional) Comparison function that defines the sort order.
25571      * Defaults to sorting by case insensitive string.
25572      */
25573     sortByKey : function(dir, fn){
25574         this._sort('key', dir, fn || function(a, b){
25575             var v1 = String(a).toUpperCase(), v2 = String(b).toUpperCase();
25576             return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0);
25577         });
25578     }
25579 });
25580
25581 /**
25582  * @class Ext.data.StoreManager
25583  * @extends Ext.util.MixedCollection
25584  * <p>Contains a collection of all stores that are created that have an identifier.
25585  * An identifier can be assigned by setting the {@link Ext.data.AbstractStore#storeId storeId} 
25586  * property. When a store is in the StoreManager, it can be referred to via it's identifier:
25587  * <pre><code>
25588 Ext.create('Ext.data.Store', {
25589     model: 'SomeModel',
25590     storeId: 'myStore'
25591 });
25592
25593 var store = Ext.data.StoreManager.lookup('myStore');
25594  * </code></pre>
25595  * Also note that the {@link #lookup} method is aliased to {@link Ext#getStore} for convenience.</p>
25596  * <p>
25597  * If a store is registered with the StoreManager, you can also refer to the store by it's identifier when
25598  * registering it with any Component that consumes data from a store:
25599  * <pre><code>
25600 Ext.create('Ext.data.Store', {
25601     model: 'SomeModel',
25602     storeId: 'myStore'
25603 });
25604
25605 Ext.create('Ext.view.View', {
25606     store: 'myStore',
25607     // other configuration here
25608 });
25609  * </code></pre>
25610  * </p>
25611  * @singleton
25612  * @docauthor Evan Trimboli <evan@sencha.com>
25613  * TODO: Make this an AbstractMgr
25614  */
25615 Ext.define('Ext.data.StoreManager', {
25616     extend: 'Ext.util.MixedCollection',
25617     alternateClassName: ['Ext.StoreMgr', 'Ext.data.StoreMgr', 'Ext.StoreManager'],
25618     singleton: true,
25619     uses: ['Ext.data.ArrayStore'],
25620     
25621     /**
25622      * @cfg {Object} listeners @hide
25623      */
25624
25625     /**
25626      * Registers one or more Stores with the StoreManager. You do not normally need to register stores
25627      * manually.  Any store initialized with a {@link Ext.data.Store#storeId} will be auto-registered. 
25628      * @param {Ext.data.Store} store1 A Store instance
25629      * @param {Ext.data.Store} store2 (optional)
25630      * @param {Ext.data.Store} etc... (optional)
25631      */
25632     register : function() {
25633         for (var i = 0, s; (s = arguments[i]); i++) {
25634             this.add(s);
25635         }
25636     },
25637
25638     /**
25639      * Unregisters one or more Stores with the StoreManager
25640      * @param {String/Object} id1 The id of the Store, or a Store instance
25641      * @param {String/Object} id2 (optional)
25642      * @param {String/Object} etc... (optional)
25643      */
25644     unregister : function() {
25645         for (var i = 0, s; (s = arguments[i]); i++) {
25646             this.remove(this.lookup(s));
25647         }
25648     },
25649
25650     /**
25651      * Gets a registered Store by id
25652      * @param {String/Object} id The id of the Store, or a Store instance, or a store configuration
25653      * @return {Ext.data.Store}
25654      */
25655     lookup : function(store) {
25656         // handle the case when we are given an array or an array of arrays.
25657         if (Ext.isArray(store)) {
25658             var fields = ['field1'], 
25659                 expand = !Ext.isArray(store[0]),
25660                 data = store,
25661                 i,
25662                 len;
25663                 
25664             if(expand){
25665                 data = [];
25666                 for (i = 0, len = store.length; i < len; ++i) {
25667                     data.push([store[i]]);
25668                 }
25669             } else {
25670                 for(i = 2, len = store[0].length; i <= len; ++i){
25671                     fields.push('field' + i);
25672                 }
25673             }
25674             return Ext.create('Ext.data.ArrayStore', {
25675                 data  : data,
25676                 fields: fields,
25677                 autoDestroy: true,
25678                 autoCreated: true,
25679                 expanded: expand
25680             });
25681         }
25682         
25683         if (Ext.isString(store)) {
25684             // store id
25685             return this.get(store);
25686         } else {
25687             // store instance or store config
25688             return Ext.data.AbstractStore.create(store);
25689         }
25690     },
25691
25692     // getKey implementation for MixedCollection
25693     getKey : function(o) {
25694          return o.storeId;
25695     }
25696 }, function() {    
25697     /**
25698      * <p>Creates a new store for the given id and config, then registers it with the {@link Ext.data.StoreManager Store Mananger}. 
25699      * Sample usage:</p>
25700     <pre><code>
25701     Ext.regStore('AllUsers', {
25702         model: 'User'
25703     });
25704
25705     //the store can now easily be used throughout the application
25706     new Ext.List({
25707         store: 'AllUsers',
25708         ... other config
25709     });
25710     </code></pre>
25711      * @param {String} id The id to set on the new store
25712      * @param {Object} config The store config
25713      * @param {Constructor} cls The new Component class.
25714      * @member Ext
25715      * @method regStore
25716      */
25717     Ext.regStore = function(name, config) {
25718         var store;
25719
25720         if (Ext.isObject(name)) {
25721             config = name;
25722         } else {
25723             config.storeId = name;
25724         }
25725
25726         if (config instanceof Ext.data.Store) {
25727             store = config;
25728         } else {
25729             store = Ext.create('Ext.data.Store', config);
25730         }
25731
25732         return Ext.data.StoreManager.register(store);
25733     };
25734
25735     /**
25736      * Gets a registered Store by id (shortcut to {@link #lookup})
25737      * @param {String/Object} id The id of the Store, or a Store instance
25738      * @return {Ext.data.Store}
25739      * @member Ext
25740      * @method getStore
25741      */
25742     Ext.getStore = function(name) {
25743         return Ext.data.StoreManager.lookup(name);
25744     };
25745 });
25746
25747 /**
25748  * @class Ext.LoadMask
25749  * A simple utility class for generically masking elements while loading data.  If the {@link #store}
25750  * config option is specified, the masking will be automatically synchronized with the store's loading
25751  * process and the mask element will be cached for reuse.
25752  * <p>Example usage:</p>
25753  * <pre><code>
25754 // Basic mask:
25755 var myMask = new Ext.LoadMask(Ext.getBody(), {msg:"Please wait..."});
25756 myMask.show();
25757 </code></pre>
25758
25759  * @constructor
25760  * Create a new LoadMask
25761  * @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.
25762  * @param {Object} config The config object
25763  */
25764
25765 Ext.define('Ext.LoadMask', {
25766
25767     /* Begin Definitions */
25768
25769     mixins: {
25770         observable: 'Ext.util.Observable'
25771     },
25772
25773     requires: ['Ext.data.StoreManager'],
25774
25775     /* End Definitions */
25776
25777     /**
25778      * @cfg {Ext.data.Store} store
25779      * Optional Store to which the mask is bound. The mask is displayed when a load request is issued, and
25780      * hidden on either load success, or load fail.
25781      */
25782
25783     /**
25784      * @cfg {String} msg
25785      * The text to display in a centered loading message box (defaults to 'Loading...')
25786      */
25787     msg : 'Loading...',
25788     /**
25789      * @cfg {String} msgCls
25790      * The CSS class to apply to the loading message element (defaults to "x-mask-loading")
25791      */
25792     msgCls : Ext.baseCSSPrefix + 'mask-loading',
25793     
25794     /**
25795      * @cfg {Boolean} useMsg
25796      * Whether or not to use a loading message class or simply mask the bound element.
25797      */
25798     useMsg: true,
25799
25800     /**
25801      * Read-only. True if the mask is currently disabled so that it will not be displayed (defaults to false)
25802      * @type Boolean
25803      */
25804     disabled: false,
25805
25806     constructor : function(el, config) {
25807         var me = this;
25808
25809         if (el.isComponent) {
25810             me.bindComponent(el);
25811         } else {
25812             me.el = Ext.get(el);
25813         }
25814         Ext.apply(me, config);
25815
25816         me.addEvents('beforeshow', 'show', 'hide');
25817         if (me.store) {
25818             me.bindStore(me.store, true);
25819         }
25820         me.mixins.observable.constructor.call(me, config);
25821     },
25822
25823     bindComponent: function(comp) {
25824         var me = this,
25825             listeners = {
25826                 resize: me.onComponentResize,
25827                 scope: me
25828             };
25829
25830         if (comp.el) {
25831             me.onComponentRender(comp);
25832         } else {
25833             listeners.render = {
25834                 fn: me.onComponentRender,
25835                 scope: me,
25836                 single: true
25837             };
25838         }
25839         me.mon(comp, listeners);
25840     },
25841
25842     /**
25843      * @private
25844      * Called if we were configured with a Component, and that Component was not yet rendered. Collects the element to mask.
25845      */
25846     onComponentRender: function(comp) {
25847         this.el = comp.getContentTarget();
25848     },
25849
25850     /**
25851      * @private
25852      * Called when this LoadMask's Component is resized. The isMasked method also re-centers any displayed message.
25853      */
25854     onComponentResize: function(comp, w, h) {
25855         this.el.isMasked();
25856     },
25857
25858     /**
25859      * Changes the data store bound to this LoadMask.
25860      * @param {Store} store The store to bind to this LoadMask
25861      */
25862     bindStore : function(store, initial) {
25863         var me = this;
25864
25865         if (!initial && me.store) {
25866             me.mun(me.store, {
25867                 scope: me,
25868                 beforeload: me.onBeforeLoad,
25869                 load: me.onLoad,
25870                 exception: me.onLoad
25871             });
25872             if(!store) {
25873                 me.store = null;
25874             }
25875         }
25876         if (store) {
25877             store = Ext.data.StoreManager.lookup(store);
25878             me.mon(store, {
25879                 scope: me,
25880                 beforeload: me.onBeforeLoad,
25881                 load: me.onLoad,
25882                 exception: me.onLoad
25883             });
25884
25885         }
25886         me.store = store;
25887         if (store && store.isLoading()) {
25888             me.onBeforeLoad();
25889         }
25890     },
25891
25892     /**
25893      * Disables the mask to prevent it from being displayed
25894      */
25895     disable : function() {
25896         var me = this;
25897
25898        me.disabled = true;
25899        if (me.loading) {
25900            me.onLoad();
25901        }
25902     },
25903
25904     /**
25905      * Enables the mask so that it can be displayed
25906      */
25907     enable : function() {
25908         this.disabled = false;
25909     },
25910
25911     /**
25912      * Method to determine whether this LoadMask is currently disabled.
25913      * @return {Boolean} the disabled state of this LoadMask.
25914      */
25915     isDisabled : function() {
25916         return this.disabled;
25917     },
25918
25919     // private
25920     onLoad : function() {
25921         var me = this;
25922
25923         me.loading = false;
25924         me.el.unmask();
25925         me.fireEvent('hide', me, me.el, me.store);
25926     },
25927
25928     // private
25929     onBeforeLoad : function() {
25930         var me = this;
25931
25932         if (!me.disabled && !me.loading && me.fireEvent('beforeshow', me, me.el, me.store) !== false) {
25933             if (me.useMsg) {
25934                 me.el.mask(me.msg, me.msgCls, false);
25935             } else {
25936                 me.el.mask();
25937             }
25938             
25939             me.fireEvent('show', me, me.el, me.store);
25940             me.loading = true;
25941         }
25942     },
25943
25944     /**
25945      * Show this LoadMask over the configured Element.
25946      */
25947     show: function() {
25948         this.onBeforeLoad();
25949     },
25950
25951     /**
25952      * Hide this LoadMask.
25953      */
25954     hide: function() {
25955         this.onLoad();
25956     },
25957
25958     // private
25959     destroy : function() {
25960         this.hide();
25961         this.clearListeners();
25962     }
25963 });
25964
25965 /**
25966  * @class Ext.ComponentLoader
25967  * @extends Ext.ElementLoader
25968  * 
25969  * This class is used to load content via Ajax into a {@link Ext.Component}. In general 
25970  * this class will not be instanced directly, rather a loader configuration will be passed to the
25971  * constructor of the {@link Ext.Component}.
25972  * 
25973  * ## HTML Renderer
25974  * By default, the content loaded will be processed as raw html. The response text
25975  * from the request is taken and added to the component. This can be used in
25976  * conjunction with the {@link #scripts} option to execute any inline scripts in
25977  * the resulting content. Using this renderer has the same effect as passing the
25978  * {@link Ext.Component#html} configuration option.
25979  * 
25980  * ## Data Renderer
25981  * This renderer allows content to be added by using JSON data and a {@link Ext.XTemplate}.
25982  * The content received from the response is passed to the {@link Ext.Component#update} method.
25983  * This content is run through the attached {@link Ext.Component#tpl} and the data is added to
25984  * the Component. Using this renderer has the same effect as using the {@link Ext.Component#data}
25985  * configuration in conjunction with a {@link Ext.Component#tpl}.
25986  * 
25987  * ## Component Renderer
25988  * This renderer can only be used with a {@link Ext.Container} and subclasses. It allows for
25989  * Components to be loaded remotely into a Container. The response is expected to be a single/series of
25990  * {@link Ext.Component} configuration objects. When the response is received, the data is decoded
25991  * and then passed to {@link Ext.Container#add}. Using this renderer has the same effect as specifying
25992  * the {@link Ext.Container#items} configuration on a Container. 
25993  * 
25994  * ## Custom Renderer
25995  * A custom function can be passed to handle any other special case, see the {@link #renderer} option.
25996  * 
25997  * ## Example Usage
25998  *     new Ext.Component({
25999  *         tpl: '{firstName} - {lastName}',
26000  *         loader: {
26001  *             url: 'myPage.php',
26002  *             renderer: 'data',
26003  *             params: {
26004  *                 userId: 1
26005  *             }
26006  *         }
26007  *     });
26008  */
26009 Ext.define('Ext.ComponentLoader', {
26010
26011     /* Begin Definitions */
26012     
26013     extend: 'Ext.ElementLoader',
26014
26015     statics: {
26016         Renderer: {
26017             Data: function(loader, response, active){
26018                 var success = true;
26019                 try {
26020                     loader.getTarget().update(Ext.decode(response.responseText));
26021                 } catch (e) {
26022                     success = false;
26023                 }
26024                 return success;
26025             },
26026
26027             Component: function(loader, response, active){
26028                 var success = true,
26029                     target = loader.getTarget(),
26030                     items = [];
26031
26032                 if (!target.isContainer) {
26033                     Ext.Error.raise({
26034                         target: target,
26035                         msg: 'Components can only be loaded into a container'
26036                     });
26037                 }
26038
26039                 try {
26040                     items = Ext.decode(response.responseText);
26041                 } catch (e) {
26042                     success = false;
26043                 }
26044
26045                 if (success) {
26046                     if (active.removeAll) {
26047                         target.removeAll();
26048                     }
26049                     target.add(items);
26050                 }
26051                 return success;
26052             }
26053         }
26054     },
26055
26056     /* End Definitions */
26057
26058     /**
26059      * @cfg {Ext.Component/String} target The target {@link Ext.Component} for the loader. Defaults to <tt>null</tt>.
26060      * If a string is passed it will be looked up via the id.
26061      */
26062     target: null,
26063
26064     /**
26065      * @cfg {Mixed} loadMask True or a {@link Ext.LoadMask} configuration to enable masking during loading. Defaults to <tt>false</tt>.
26066      */
26067     loadMask: false,
26068     
26069     /**
26070      * @cfg {Boolean} scripts True to parse any inline script tags in the response. This only used when using the html
26071      * {@link #renderer}.
26072      */
26073
26074     /**
26075      * @cfg {String/Function} renderer
26076
26077 The type of content that is to be loaded into, which can be one of 3 types:
26078
26079 + **html** : Loads raw html content, see {@link Ext.Component#html}
26080 + **data** : Loads raw html content, see {@link Ext.Component#data}
26081 + **component** : Loads child {Ext.Component} instances. This option is only valid when used with a Container.
26082
26083 Defaults to `html`.
26084
26085 Alternatively, you can pass a function which is called with the following parameters.
26086
26087 + loader - Loader instance
26088 + response - The server response
26089 + active - The active request
26090
26091 The function must return false is loading is not successful. Below is a sample of using a custom renderer:
26092
26093     new Ext.Component({
26094         loader: {
26095             url: 'myPage.php',
26096             renderer: function(loader, response, active) {
26097                 var text = response.responseText;
26098                 loader.getTarget().update('The response is ' + text);
26099                 return true;
26100             }
26101         }
26102     });
26103      * @markdown
26104      */
26105     renderer: 'html',
26106
26107     /**
26108      * Set a {Ext.Component} as the target of this loader. Note that if the target is changed,
26109      * any active requests will be aborted.
26110      * @param {String/Ext.Component} target The component to be the target of this loader. If a string is passed
26111      * it will be looked up via its id.
26112      */
26113     setTarget: function(target){
26114         var me = this;
26115         
26116         if (Ext.isString(target)) {
26117             target = Ext.getCmp(target);
26118         }
26119
26120         if (me.target && me.target != target) {
26121             me.abort();
26122         }
26123         me.target = target;
26124     },
26125     
26126     // inherit docs
26127     removeMask: function(){
26128         this.target.setLoading(false);
26129     },
26130     
26131     /**
26132      * Add the mask on the target
26133      * @private
26134      * @param {Mixed} mask The mask configuration
26135      */
26136     addMask: function(mask){
26137         this.target.setLoading(mask);
26138     },
26139
26140     /**
26141      * Get the target of this loader.
26142      * @return {Ext.Component} target The target, null if none exists.
26143      */
26144     
26145     setOptions: function(active, options){
26146         active.removeAll = Ext.isDefined(options.removeAll) ? options.removeAll : this.removeAll;
26147     },
26148
26149     /**
26150      * Gets the renderer to use
26151      * @private
26152      * @param {String/Function} renderer The renderer to use
26153      * @return {Function} A rendering function to use.
26154      */
26155     getRenderer: function(renderer){
26156         if (Ext.isFunction(renderer)) {
26157             return renderer;
26158         }
26159
26160         var renderers = this.statics().Renderer;
26161         switch (renderer) {
26162             case 'component':
26163                 return renderers.Component;
26164             case 'data':
26165                 return renderers.Data;
26166             default:
26167                 return Ext.ElementLoader.Renderer.Html;
26168         }
26169     }
26170 });
26171
26172 /**
26173  * @class Ext.layout.component.Auto
26174  * @extends Ext.layout.component.Component
26175  * @private
26176  *
26177  * <p>The AutoLayout is the default layout manager delegated by {@link Ext.Component} to
26178  * render any child Elements when no <tt>{@link Ext.Component#layout layout}</tt> is configured.</p>
26179  */
26180
26181 Ext.define('Ext.layout.component.Auto', {
26182
26183     /* Begin Definitions */
26184
26185     alias: 'layout.autocomponent',
26186
26187     extend: 'Ext.layout.component.Component',
26188
26189     /* End Definitions */
26190
26191     type: 'autocomponent',
26192
26193     onLayout : function(width, height) {
26194         this.setTargetSize(width, height);
26195     }
26196 });
26197 /**
26198  * @class Ext.AbstractComponent
26199  * <p>An abstract base class which provides shared methods for Components across the Sencha product line.</p>
26200  * <p>Please refer to sub class's documentation</p>
26201  * @constructor
26202  */
26203
26204 Ext.define('Ext.AbstractComponent', {
26205
26206     /* Begin Definitions */
26207
26208     mixins: {
26209         observable: 'Ext.util.Observable',
26210         animate: 'Ext.util.Animate',
26211         state: 'Ext.state.Stateful'
26212     },
26213
26214     requires: [
26215         'Ext.PluginManager',
26216         'Ext.ComponentManager',
26217         'Ext.core.Element',
26218         'Ext.core.DomHelper',
26219         'Ext.XTemplate',
26220         'Ext.ComponentQuery',
26221         'Ext.LoadMask',
26222         'Ext.ComponentLoader',
26223         'Ext.EventManager',
26224         'Ext.layout.Layout',
26225         'Ext.layout.component.Auto'
26226     ],
26227
26228     // Please remember to add dependencies whenever you use it
26229     // I had to fix these many times already
26230     uses: [
26231         'Ext.ZIndexManager'
26232     ],
26233
26234     statics: {
26235         AUTO_ID: 1000
26236     },
26237
26238     /* End Definitions */
26239
26240     isComponent: true,
26241
26242     getAutoId: function() {
26243         return ++Ext.AbstractComponent.AUTO_ID;
26244     },
26245
26246     /**
26247      * @cfg {String} id
26248      * <p>The <b><u>unique id of this component instance</u></b> (defaults to an {@link #getId auto-assigned id}).</p>
26249      * <p>It should not be necessary to use this configuration except for singleton objects in your application.
26250      * Components created with an id may be accessed globally using {@link Ext#getCmp Ext.getCmp}.</p>
26251      * <p>Instead of using assigned ids, use the {@link #itemId} config, and {@link Ext.ComponentQuery ComponentQuery} which
26252      * provides selector-based searching for Sencha Components analogous to DOM querying. The {@link Ext.container.Container Container}
26253      * class contains {@link Ext.container.Container#down shortcut methods} to query its descendant Components by selector.</p>
26254      * <p>Note that this id will also be used as the element id for the containing HTML element
26255      * that is rendered to the page for this component. This allows you to write id-based CSS
26256      * rules to style the specific instance of this component uniquely, and also to select
26257      * sub-elements using this component's id as the parent.</p>
26258      * <p><b>Note</b>: to avoid complications imposed by a unique <tt>id</tt> also see <code>{@link #itemId}</code>.</p>
26259      * <p><b>Note</b>: to access the container of a Component see <code>{@link #ownerCt}</code>.</p>
26260      */
26261
26262     /**
26263      * @cfg {String} itemId
26264      * <p>An <tt>itemId</tt> can be used as an alternative way to get a reference to a component
26265      * when no object reference is available.  Instead of using an <code>{@link #id}</code> with
26266      * {@link Ext}.{@link Ext#getCmp getCmp}, use <code>itemId</code> with
26267      * {@link Ext.container.Container}.{@link Ext.container.Container#getComponent getComponent} which will retrieve
26268      * <code>itemId</code>'s or <tt>{@link #id}</tt>'s. Since <code>itemId</code>'s are an index to the
26269      * container's internal MixedCollection, the <code>itemId</code> is scoped locally to the container --
26270      * avoiding potential conflicts with {@link Ext.ComponentManager} which requires a <b>unique</b>
26271      * <code>{@link #id}</code>.</p>
26272      * <pre><code>
26273 var c = new Ext.panel.Panel({ //
26274     {@link Ext.Component#height height}: 300,
26275     {@link #renderTo}: document.body,
26276     {@link Ext.container.Container#layout layout}: 'auto',
26277     {@link Ext.container.Container#items items}: [
26278         {
26279             itemId: 'p1',
26280             {@link Ext.panel.Panel#title title}: 'Panel 1',
26281             {@link Ext.Component#height height}: 150
26282         },
26283         {
26284             itemId: 'p2',
26285             {@link Ext.panel.Panel#title title}: 'Panel 2',
26286             {@link Ext.Component#height height}: 150
26287         }
26288     ]
26289 })
26290 p1 = c.{@link Ext.container.Container#getComponent getComponent}('p1'); // not the same as {@link Ext#getCmp Ext.getCmp()}
26291 p2 = p1.{@link #ownerCt}.{@link Ext.container.Container#getComponent getComponent}('p2'); // reference via a sibling
26292      * </code></pre>
26293      * <p>Also see <tt>{@link #id}</tt>, <code>{@link #query}</code>, <code>{@link #down}</code> and <code>{@link #child}</code>.</p>
26294      * <p><b>Note</b>: to access the container of an item see <tt>{@link #ownerCt}</tt>.</p>
26295      */
26296
26297     /**
26298      * This Component's owner {@link Ext.container.Container Container} (defaults to undefined, and is set automatically when
26299      * this Component is added to a Container).  Read-only.
26300      * <p><b>Note</b>: to access items within the Container see <tt>{@link #itemId}</tt>.</p>
26301      * @type Ext.Container
26302      * @property ownerCt
26303      */
26304
26305     /**
26306      * @cfg {Mixed} autoEl
26307      * <p>A tag name or {@link Ext.core.DomHelper DomHelper} spec used to create the {@link #getEl Element} which will
26308      * encapsulate this Component.</p>
26309      * <p>You do not normally need to specify this. For the base classes {@link Ext.Component} and {@link Ext.container.Container},
26310      * this defaults to <b><tt>'div'</tt></b>. The more complex Sencha classes use a more complex
26311      * DOM structure specified by their own {@link #renderTpl}s.</p>
26312      * <p>This is intended to allow the developer to create application-specific utility Components encapsulated by
26313      * different DOM elements. Example usage:</p><pre><code>
26314 {
26315     xtype: 'component',
26316     autoEl: {
26317         tag: 'img',
26318         src: 'http://www.example.com/example.jpg'
26319     }
26320 }, {
26321     xtype: 'component',
26322     autoEl: {
26323         tag: 'blockquote',
26324         html: 'autoEl is cool!'
26325     }
26326 }, {
26327     xtype: 'container',
26328     autoEl: 'ul',
26329     cls: 'ux-unordered-list',
26330     items: {
26331         xtype: 'component',
26332         autoEl: 'li',
26333         html: 'First list item'
26334     }
26335 }
26336 </code></pre>
26337      */
26338
26339     /**
26340      * @cfg {Mixed} renderTpl
26341      * <p>An {@link Ext.XTemplate XTemplate} used to create the internal structure inside this Component's
26342      * encapsulating {@link #getEl Element}.</p>
26343      * <p>You do not normally need to specify this. For the base classes {@link Ext.Component}
26344      * and {@link Ext.container.Container}, this defaults to <b><code>null</code></b> which means that they will be initially rendered
26345      * with no internal structure; they render their {@link #getEl Element} empty. The more specialized ExtJS and Touch classes
26346      * which use a more complex DOM structure, provide their own template definitions.</p>
26347      * <p>This is intended to allow the developer to create application-specific utility Components with customized
26348      * internal structure.</p>
26349      * <p>Upon rendering, any created child elements may be automatically imported into object properties using the
26350      * {@link #renderSelectors} option.</p>
26351      */
26352     renderTpl: null,
26353
26354     /**
26355      * @cfg {Object} renderSelectors
26356
26357 An object containing properties specifying {@link Ext.DomQuery DomQuery} selectors which identify child elements
26358 created by the render process.
26359
26360 After the Component's internal structure is rendered according to the {@link #renderTpl}, this object is iterated through,
26361 and the found Elements are added as properties to the Component using the `renderSelector` property name.
26362
26363 For example, a Component which rendered an image, and description into its element might use the following properties
26364 coded into its prototype:
26365
26366     renderTpl: '&lt;img src="{imageUrl}" class="x-image-component-img">&lt;div class="x-image-component-desc">{description}&gt;/div&lt;',
26367
26368     renderSelectors: {
26369         image: 'img.x-image-component-img',
26370         descEl: 'div.x-image-component-desc'
26371     }
26372
26373 After rendering, the Component would have a property <code>image</code> referencing its child `img` Element,
26374 and a property `descEl` referencing the `div` Element which contains the description.
26375
26376      * @markdown
26377      */
26378
26379     /**
26380      * @cfg {Mixed} renderTo
26381      * <p>Specify the id of the element, a DOM element or an existing Element that this component
26382      * will be rendered into.</p><div><ul>
26383      * <li><b>Notes</b> : <ul>
26384      * <div class="sub-desc">Do <u>not</u> use this option if the Component is to be a child item of
26385      * a {@link Ext.container.Container Container}. It is the responsibility of the
26386      * {@link Ext.container.Container Container}'s {@link Ext.container.Container#layout layout manager}
26387      * to render and manage its child items.</div>
26388      * <div class="sub-desc">When using this config, a call to render() is not required.</div>
26389      * </ul></li>
26390      * </ul></div>
26391      * <p>See <code>{@link #render}</code> also.</p>
26392      */
26393
26394     /**
26395      * @cfg {Boolean} frame
26396      * <p>Specify as <code>true</code> to have the Component inject framing elements within the Component at render time to
26397      * provide a graphical rounded frame around the Component content.</p>
26398      * <p>This is only necessary when running on outdated, or non standard-compliant browsers such as Microsoft's Internet Explorer
26399      * prior to version 9 which do not support rounded corners natively.</p>
26400      * <p>The extra space taken up by this framing is available from the read only property {@link #frameSize}.</p>
26401      */
26402
26403     /**
26404      * <p>Read-only property indicating the width of any framing elements which were added within the encapsulating element
26405      * to provide graphical, rounded borders. See the {@link #frame} config.</p>
26406      * <p> This is an object containing the frame width in pixels for all four sides of the Component containing
26407      * the following properties:</p><div class="mdetail-params"><ul>
26408      * <li><code>top</code> The width of the top framing element in pixels.</li>
26409      * <li><code>right</code> The width of the right framing element in pixels.</li>
26410      * <li><code>bottom</code> The width of the bottom framing element in pixels.</li>
26411      * <li><code>left</code> The width of the left framing element in pixels.</li>
26412      * </ul></div>
26413      * @property frameSize
26414      * @type {Object}
26415      */
26416
26417     /**
26418      * @cfg {String/Object} componentLayout
26419      * <p>The sizing and positioning of a Component's internal Elements is the responsibility of
26420      * the Component's layout manager which sizes a Component's internal structure in response to the Component being sized.</p>
26421      * <p>Generally, developers will not use this configuration as all provided Components which need their internal
26422      * elements sizing (Such as {@link Ext.form.field.Base input fields}) come with their own componentLayout managers.</p>
26423      * <p>The {@link Ext.layout.container.Auto default layout manager} will be used on instances of the base Ext.Component class
26424      * which simply sizes the Component's encapsulating element to the height and width specified in the {@link #setSize} method.</p>
26425      */
26426
26427     /**
26428      * @cfg {Mixed} tpl
26429      * An <bold>{@link Ext.Template}</bold>, <bold>{@link Ext.XTemplate}</bold>
26430      * or an array of strings to form an Ext.XTemplate.
26431      * Used in conjunction with the <code>{@link #data}</code> and
26432      * <code>{@link #tplWriteMode}</code> configurations.
26433      */
26434
26435     /**
26436      * @cfg {Mixed} data
26437      * The initial set of data to apply to the <code>{@link #tpl}</code> to
26438      * update the content area of the Component.
26439      */
26440
26441     /**
26442      * @cfg {String} tplWriteMode The Ext.(X)Template method to use when
26443      * updating the content area of the Component. Defaults to <code>'overwrite'</code>
26444      * (see <code>{@link Ext.XTemplate#overwrite}</code>).
26445      */
26446     tplWriteMode: 'overwrite',
26447
26448     /**
26449      * @cfg {String} baseCls
26450      * The base CSS class to apply to this components's element. This will also be prepended to
26451      * elements within this component like Panel's body will get a class x-panel-body. This means
26452      * that if you create a subclass of Panel, and you want it to get all the Panels styling for the
26453      * element and the body, you leave the baseCls x-panel and use componentCls to add specific styling for this
26454      * component.
26455      */
26456     baseCls: Ext.baseCSSPrefix + 'component',
26457
26458     /**
26459      * @cfg {String} componentCls
26460      * CSS Class to be added to a components root level element to give distinction to it
26461      * via styling.
26462      */
26463
26464     /**
26465      * @cfg {String} cls
26466      * An optional extra CSS class that will be added to this component's Element (defaults to '').  This can be
26467      * useful for adding customized styles to the component or any of its children using standard CSS rules.
26468      */
26469
26470     /**
26471      * @cfg {String} overCls
26472      * An optional extra CSS class that will be added to this component's Element when the mouse moves
26473      * over the Element, and removed when the mouse moves out. (defaults to '').  This can be
26474      * useful for adding customized 'active' or 'hover' styles to the component or any of its children using standard CSS rules.
26475      */
26476
26477     /**
26478      * @cfg {String} disabledCls
26479      * CSS class to add when the Component is disabled. Defaults to 'x-item-disabled'.
26480      */
26481     disabledCls: Ext.baseCSSPrefix + 'item-disabled',
26482
26483     /**
26484      * @cfg {String/Array} ui
26485      * A set style for a component. Can be a string or an Array of multiple strings (UIs)
26486      */
26487     ui: 'default',
26488     
26489     /**
26490      * @cfg {Array} uiCls
26491      * An array of of classNames which are currently applied to this component
26492      * @private
26493      */
26494     uiCls: [],
26495     
26496     /**
26497      * @cfg {String} style
26498      * A custom style specification to be applied to this component's Element.  Should be a valid argument to
26499      * {@link Ext.core.Element#applyStyles}.
26500      * <pre><code>
26501         new Ext.panel.Panel({
26502             title: 'Some Title',
26503             renderTo: Ext.getBody(),
26504             width: 400, height: 300,
26505             layout: 'form',
26506             items: [{
26507                 xtype: 'textarea',
26508                 style: {
26509                     width: '95%',
26510                     marginBottom: '10px'
26511                 }
26512             },
26513             new Ext.button.Button({
26514                 text: 'Send',
26515                 minWidth: '100',
26516                 style: {
26517                     marginBottom: '10px'
26518                 }
26519             })
26520             ]
26521         });
26522      </code></pre>
26523      */
26524
26525     /**
26526      * @cfg {Number} width
26527      * The width of this component in pixels.
26528      */
26529
26530     /**
26531      * @cfg {Number} height
26532      * The height of this component in pixels.
26533      */
26534
26535     /**
26536      * @cfg {Number/String} border
26537      * Specifies the border for this component. The border can be a single numeric value to apply to all sides or
26538      * it can be a CSS style specification for each style, for example: '10 5 3 10'.
26539      */
26540
26541     /**
26542      * @cfg {Number/String} padding
26543      * Specifies the padding for this component. The padding can be a single numeric value to apply to all sides or
26544      * it can be a CSS style specification for each style, for example: '10 5 3 10'.
26545      */
26546
26547     /**
26548      * @cfg {Number/String} margin
26549      * Specifies the margin for this component. The margin can be a single numeric value to apply to all sides or
26550      * it can be a CSS style specification for each style, for example: '10 5 3 10'.
26551      */
26552
26553     /**
26554      * @cfg {Boolean} hidden
26555      * Defaults to false.
26556      */
26557     hidden: false,
26558
26559     /**
26560      * @cfg {Boolean} disabled
26561      * Defaults to false.
26562      */
26563     disabled: false,
26564
26565     /**
26566      * @cfg {Boolean} draggable
26567      * Allows the component to be dragged.
26568      */
26569
26570     /**
26571      * Read-only property indicating whether or not the component can be dragged
26572      * @property draggable
26573      * @type {Boolean}
26574      */
26575     draggable: false,
26576
26577     /**
26578      * @cfg {Boolean} floating
26579      * Create the Component as a floating and use absolute positioning.
26580      * Defaults to false.
26581      */
26582     floating: false,
26583
26584     /**
26585      * @cfg {String} hideMode
26586      * A String which specifies how this Component's encapsulating DOM element will be hidden.
26587      * Values may be<div class="mdetail-params"><ul>
26588      * <li><code>'display'</code> : The Component will be hidden using the <code>display: none</code> style.</li>
26589      * <li><code>'visibility'</code> : The Component will be hidden using the <code>visibility: hidden</code> style.</li>
26590      * <li><code>'offsets'</code> : The Component will be hidden by absolutely positioning it out of the visible area of the document. This
26591      * is useful when a hidden Component must maintain measurable dimensions. Hiding using <code>display</code> results
26592      * in a Component having zero dimensions.</li></ul></div>
26593      * Defaults to <code>'display'</code>.
26594      */
26595     hideMode: 'display',
26596
26597     /**
26598      * @cfg {String} contentEl
26599      * <p>Optional. Specify an existing HTML element, or the <code>id</code> of an existing HTML element to use as the content
26600      * for this component.</p>
26601      * <ul>
26602      * <li><b>Description</b> :
26603      * <div class="sub-desc">This config option is used to take an existing HTML element and place it in the layout element
26604      * 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>
26605      * <li><b>Notes</b> :
26606      * <div class="sub-desc">The specified HTML element is appended to the layout element of the component <i>after any configured
26607      * {@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>
26608      * <div class="sub-desc">The specified HTML element used will not participate in any <code><b>{@link Ext.container.Container#layout layout}</b></code>
26609      * 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>
26610      * <div class="sub-desc">Add either the <code>x-hidden</code> or the <code>x-hide-display</code> CSS class to
26611      * prevent a brief flicker of the content before it is rendered to the panel.</div></li>
26612      * </ul>
26613      */
26614
26615     /**
26616      * @cfg {String/Object} html
26617      * An HTML fragment, or a {@link Ext.core.DomHelper DomHelper} specification to use as the layout element
26618      * content (defaults to ''). The HTML content is added after the component is rendered,
26619      * so the document will not contain this HTML at the time the {@link #render} event is fired.
26620      * This content is inserted into the body <i>before</i> any configured {@link #contentEl} is appended.
26621      */
26622
26623     /**
26624      * @cfg {String} styleHtmlContent
26625      * True to automatically style the html inside the content target of this component (body for panels).
26626      * Defaults to false.
26627      */
26628     styleHtmlContent: false,
26629
26630     /**
26631      * @cfg {String} styleHtmlCls
26632      * The class that is added to the content target when you set styleHtmlContent to true.
26633      * Defaults to 'x-html'
26634      */
26635     styleHtmlCls: Ext.baseCSSPrefix + 'html',
26636
26637     /**
26638      * @cfg {Number} minHeight
26639      * <p>The minimum value in pixels which this Component will set its height to.</p>
26640      * <p><b>Warning:</b> This will override any size management applied by layout managers.</p>
26641      */
26642     /**
26643      * @cfg {Number} minWidth
26644      * <p>The minimum value in pixels which this Component will set its width to.</p>
26645      * <p><b>Warning:</b> This will override any size management applied by layout managers.</p>
26646      */
26647     /**
26648      * @cfg {Number} maxHeight
26649      * <p>The maximum value in pixels which this Component will set its height to.</p>
26650      * <p><b>Warning:</b> This will override any size management applied by layout managers.</p>
26651      */
26652     /**
26653      * @cfg {Number} maxWidth
26654      * <p>The maximum value in pixels which this Component will set its width to.</p>
26655      * <p><b>Warning:</b> This will override any size management applied by layout managers.</p>
26656      */
26657
26658     /**
26659      * @cfg {Ext.ComponentLoader/Object} loader
26660      * A configuration object or an instance of a {@link Ext.ComponentLoader} to load remote
26661      * content for this Component.
26662      */
26663
26664      // @private
26665      allowDomMove: true,
26666
26667      /**
26668       * @cfg {Boolean} autoShow True to automatically show the component upon creation.
26669       * This config option may only be used for {@link #floating} components or components
26670       * that use {@link #autoRender}. Defaults to <tt>false</tt>.
26671       */
26672      autoShow: false,
26673
26674     /**
26675      * @cfg {Mixed} autoRender
26676      * <p>This config is intended mainly for {@link #floating} Components which may or may not be shown. Instead
26677      * of using {@link #renderTo} in the configuration, and rendering upon construction, this allows a Component
26678      * to render itself upon first <i>{@link #show}</i>.</p>
26679      * <p>Specify as <code>true</code> to have this Component render to the document body upon first show.</p>
26680      * <p>Specify as an element, or the ID of an element to have this Component render to a specific element upon first show.</p>
26681      * <p><b>This defaults to <code>true</code> for the {@link Ext.window.Window Window} class.</b></p>
26682      */
26683      autoRender: false,
26684
26685      needsLayout: false,
26686
26687     /**
26688      * @cfg {Object/Array} plugins
26689      * An object or array of objects that will provide custom functionality for this component.  The only
26690      * requirement for a valid plugin is that it contain an init method that accepts a reference of type Ext.Component.
26691      * When a component is created, if any plugins are available, the component will call the init method on each
26692      * plugin, passing a reference to itself.  Each plugin can then call methods or respond to events on the
26693      * component as needed to provide its functionality.
26694      */
26695
26696     /**
26697      * Read-only property indicating whether or not the component has been rendered.
26698      * @property rendered
26699      * @type {Boolean}
26700      */
26701     rendered: false,
26702
26703     weight: 0,
26704
26705     trimRe: /^\s+|\s+$/g,
26706     spacesRe: /\s+/,
26707     
26708     
26709     /**
26710      * This is an internal flag that you use when creating custom components.
26711      * By default this is set to true which means that every component gets a mask when its disabled.
26712      * Components like FieldContainer, FieldSet, Field, Button, Tab override this property to false
26713      * since they want to implement custom disable logic.
26714      * @property maskOnDisable
26715      * @type {Boolean}
26716      */     
26717     maskOnDisable: true,
26718
26719     constructor : function(config) {
26720         var me = this,
26721             i, len;
26722
26723         config = config || {};
26724         me.initialConfig = config;
26725         Ext.apply(me, config);
26726
26727         me.addEvents(
26728             /**
26729              * @event beforeactivate
26730              * Fires before a Component has been visually activated.
26731              * Returning false from an event listener can prevent the activate
26732              * from occurring.
26733              * @param {Ext.Component} this
26734              */
26735              'beforeactivate',
26736             /**
26737              * @event activate
26738              * Fires after a Component has been visually activated.
26739              * @param {Ext.Component} this
26740              */
26741              'activate',
26742             /**
26743              * @event beforedeactivate
26744              * Fires before a Component has been visually deactivated.
26745              * Returning false from an event listener can prevent the deactivate
26746              * from occurring.
26747              * @param {Ext.Component} this
26748              */
26749              'beforedeactivate',
26750             /**
26751              * @event deactivate
26752              * Fires after a Component has been visually deactivated.
26753              * @param {Ext.Component} this
26754              */
26755              'deactivate',
26756             /**
26757              * @event added
26758              * Fires after a Component had been added to a Container.
26759              * @param {Ext.Component} this
26760              * @param {Ext.container.Container} container Parent Container
26761              * @param {Number} pos position of Component
26762              */
26763              'added',
26764             /**
26765              * @event disable
26766              * Fires after the component is disabled.
26767              * @param {Ext.Component} this
26768              */
26769              'disable',
26770             /**
26771              * @event enable
26772              * Fires after the component is enabled.
26773              * @param {Ext.Component} this
26774              */
26775              'enable',
26776             /**
26777              * @event beforeshow
26778              * Fires before the component is shown when calling the {@link #show} method.
26779              * Return false from an event handler to stop the show.
26780              * @param {Ext.Component} this
26781              */
26782              'beforeshow',
26783             /**
26784              * @event show
26785              * Fires after the component is shown when calling the {@link #show} method.
26786              * @param {Ext.Component} this
26787              */
26788              'show',
26789             /**
26790              * @event beforehide
26791              * Fires before the component is hidden when calling the {@link #hide} method.
26792              * Return false from an event handler to stop the hide.
26793              * @param {Ext.Component} this
26794              */
26795              'beforehide',
26796             /**
26797              * @event hide
26798              * Fires after the component is hidden.
26799              * Fires after the component is hidden when calling the {@link #hide} method.
26800              * @param {Ext.Component} this
26801              */
26802              'hide',
26803             /**
26804              * @event removed
26805              * Fires when a component is removed from an Ext.container.Container
26806              * @param {Ext.Component} this
26807              * @param {Ext.container.Container} ownerCt Container which holds the component
26808              */
26809              'removed',
26810             /**
26811              * @event beforerender
26812              * Fires before the component is {@link #rendered}. Return false from an
26813              * event handler to stop the {@link #render}.
26814              * @param {Ext.Component} this
26815              */
26816              'beforerender',
26817             /**
26818              * @event render
26819              * Fires after the component markup is {@link #rendered}.
26820              * @param {Ext.Component} this
26821              */
26822              'render',
26823             /**
26824              * @event afterrender
26825              * <p>Fires after the component rendering is finished.</p>
26826              * <p>The afterrender event is fired after this Component has been {@link #rendered}, been postprocesed
26827              * by any afterRender method defined for the Component.</p>
26828              * @param {Ext.Component} this
26829              */
26830              'afterrender',
26831             /**
26832              * @event beforedestroy
26833              * Fires before the component is {@link #destroy}ed. Return false from an event handler to stop the {@link #destroy}.
26834              * @param {Ext.Component} this
26835              */
26836              'beforedestroy',
26837             /**
26838              * @event destroy
26839              * Fires after the component is {@link #destroy}ed.
26840              * @param {Ext.Component} this
26841              */
26842              'destroy',
26843             /**
26844              * @event resize
26845              * Fires after the component is resized.
26846              * @param {Ext.Component} this
26847              * @param {Number} adjWidth The box-adjusted width that was set
26848              * @param {Number} adjHeight The box-adjusted height that was set
26849              */
26850              'resize',
26851             /**
26852              * @event move
26853              * Fires after the component is moved.
26854              * @param {Ext.Component} this
26855              * @param {Number} x The new x position
26856              * @param {Number} y The new y position
26857              */
26858              'move'
26859         );
26860
26861         me.getId();
26862
26863         me.mons = [];
26864         me.additionalCls = [];
26865         me.renderData = me.renderData || {};
26866         me.renderSelectors = me.renderSelectors || {};
26867
26868         if (me.plugins) {
26869             me.plugins = [].concat(me.plugins);
26870             for (i = 0, len = me.plugins.length; i < len; i++) {
26871                 me.plugins[i] = me.constructPlugin(me.plugins[i]);
26872             }
26873         }
26874         
26875         me.initComponent();
26876
26877         // ititComponent gets a chance to change the id property before registering
26878         Ext.ComponentManager.register(me);
26879
26880         // Dont pass the config so that it is not applied to 'this' again
26881         me.mixins.observable.constructor.call(me);
26882         me.mixins.state.constructor.call(me, config);
26883
26884         // Move this into Observable?
26885         if (me.plugins) {
26886             me.plugins = [].concat(me.plugins);
26887             for (i = 0, len = me.plugins.length; i < len; i++) {
26888                 me.plugins[i] = me.initPlugin(me.plugins[i]);
26889             }
26890         }
26891
26892         me.loader = me.getLoader();
26893
26894         if (me.renderTo) {
26895             me.render(me.renderTo);
26896         }
26897
26898         if (me.autoShow) {
26899             me.show();
26900         }
26901         
26902         if (Ext.isDefined(me.disabledClass)) {
26903             if (Ext.isDefined(Ext.global.console)) {
26904                 Ext.global.console.warn('Ext.Component: disabledClass has been deprecated. Please use disabledCls.');
26905             }
26906             me.disabledCls = me.disabledClass;
26907             delete me.disabledClass;
26908         }
26909     },
26910
26911     initComponent: Ext.emptyFn,
26912
26913     show: Ext.emptyFn,
26914
26915     animate: function(animObj) {
26916         var me = this,
26917             to;
26918
26919         animObj = animObj || {};
26920         to = animObj.to || {};
26921
26922         if (Ext.fx.Manager.hasFxBlock(me.id)) {
26923             return me;
26924         }
26925         // Special processing for animating Component dimensions.
26926         if (!animObj.dynamic && (to.height || to.width)) {
26927             var curWidth = me.getWidth(),
26928                 w = curWidth,
26929                 curHeight = me.getHeight(),
26930                 h = curHeight,
26931                 needsResize = false;
26932
26933             if (to.height && to.height > curHeight) {
26934                 h = to.height;
26935                 needsResize = true;
26936             }
26937             if (to.width && to.width > curWidth) {
26938                 w = to.width;
26939                 needsResize = true;
26940             }
26941
26942             // If any dimensions are being increased, we must resize the internal structure
26943             // of the Component, but then clip it by sizing its encapsulating element back to original dimensions.
26944             // The animation will then progressively reveal the larger content.
26945             if (needsResize) {
26946                 var clearWidth = !Ext.isNumber(me.width),
26947                     clearHeight = !Ext.isNumber(me.height);
26948
26949                 me.componentLayout.childrenChanged = true;
26950                 me.setSize(w, h, me.ownerCt);
26951                 me.el.setSize(curWidth, curHeight);
26952                 if (clearWidth) {
26953                     delete me.width;
26954                 }
26955                 if (clearHeight) {
26956                     delete me.height;
26957                 }
26958             }
26959         }
26960         return me.mixins.animate.animate.apply(me, arguments);
26961     },
26962
26963     /**
26964      * <p>This method finds the topmost active layout who's processing will eventually determine the size and position of this
26965      * Component.<p>
26966      * <p>This method is useful when dynamically adding Components into Containers, and some processing must take place after the
26967      * final sizing and positioning of the Component has been performed.</p>
26968      * @returns
26969      */
26970     findLayoutController: function() {
26971         return this.findParentBy(function(c) {
26972             // Return true if we are at the root of the Container tree
26973             // or this Container's layout is busy but the next one up is not.
26974             return !c.ownerCt || (c.layout.layoutBusy && !c.ownerCt.layout.layoutBusy);
26975         });
26976     },
26977
26978     onShow : function() {
26979         // Layout if needed
26980         var needsLayout = this.needsLayout;
26981         if (Ext.isObject(needsLayout)) {
26982             this.doComponentLayout(needsLayout.width, needsLayout.height, needsLayout.isSetSize, needsLayout.ownerCt);
26983         }
26984     },
26985
26986     constructPlugin: function(plugin) {
26987         if (plugin.ptype && typeof plugin.init != 'function') {
26988             plugin.cmp = this;
26989             plugin = Ext.PluginManager.create(plugin);
26990         }
26991         else if (typeof plugin == 'string') {
26992             plugin = Ext.PluginManager.create({
26993                 ptype: plugin,
26994                 cmp: this
26995             });
26996         }
26997         return plugin;
26998     },
26999
27000
27001     // @private
27002     initPlugin : function(plugin) {
27003         plugin.init(this);
27004
27005         return plugin;
27006     },
27007
27008     /**
27009      * Handles autoRender.
27010      * Floating Components may have an ownerCt. If they are asking to be constrained, constrain them within that
27011      * ownerCt, and have their z-index managed locally. Floating Components are always rendered to document.body
27012      */
27013     doAutoRender: function() {
27014         var me = this;
27015         if (me.floating) {
27016             me.render(document.body);
27017         } else {
27018             me.render(Ext.isBoolean(me.autoRender) ? Ext.getBody() : me.autoRender);
27019         }
27020     },
27021
27022     // @private
27023     render : function(container, position) {
27024         var me = this;
27025
27026         if (!me.rendered && me.fireEvent('beforerender', me) !== false) {
27027             // If this.el is defined, we want to make sure we are dealing with
27028             // an Ext Element.
27029             if (me.el) {
27030                 me.el = Ext.get(me.el);
27031             }
27032
27033             // Perform render-time processing for floating Components
27034             if (me.floating) {
27035                 me.onFloatRender();
27036             }
27037
27038             container = me.initContainer(container);
27039
27040             me.onRender(container, position);
27041
27042             // Tell the encapsulating element to hide itself in the way the Component is configured to hide
27043             // This means DISPLAY, VISIBILITY or OFFSETS.
27044             me.el.setVisibilityMode(Ext.core.Element[me.hideMode.toUpperCase()]);
27045
27046             if (me.overCls) {
27047                 me.el.hover(me.addOverCls, me.removeOverCls, me);
27048             }
27049
27050             me.fireEvent('render', me);
27051
27052             me.initContent();
27053
27054             me.afterRender(container);
27055             me.fireEvent('afterrender', me);
27056
27057             me.initEvents();
27058
27059             if (me.hidden) {
27060                 // Hiding during the render process should not perform any ancillary
27061                 // actions that the full hide process does; It is not hiding, it begins in a hidden state.'
27062                 // So just make the element hidden according to the configured hideMode
27063                 me.el.hide();
27064             }
27065
27066             if (me.disabled) {
27067                 // pass silent so the event doesn't fire the first time.
27068                 me.disable(true);
27069             }
27070         }
27071         return me;
27072     },
27073
27074     // @private
27075     onRender : function(container, position) {
27076         var me = this,
27077             el = me.el,
27078             cls = me.initCls(),
27079             styles = me.initStyles(),
27080             renderTpl, renderData, i;
27081
27082         position = me.getInsertPosition(position);
27083
27084         if (!el) {
27085             if (position) {
27086                 el = Ext.core.DomHelper.insertBefore(position, me.getElConfig(), true);
27087             }
27088             else {
27089                 el = Ext.core.DomHelper.append(container, me.getElConfig(), true);
27090             }
27091         }
27092         else if (me.allowDomMove !== false) {
27093             if (position) {
27094                 container.dom.insertBefore(el.dom, position);
27095             } else {
27096                 container.dom.appendChild(el.dom);
27097             }
27098         }
27099
27100         if (Ext.scopeResetCSS && !me.ownerCt) {
27101             // If this component's el is the body element, we add the reset class to the html tag
27102             if (el.dom == Ext.getBody().dom) {
27103                 el.parent().addCls(Ext.baseCSSPrefix + 'reset');
27104             }
27105             else {
27106                 // Else we wrap this element in an element that adds the reset class.
27107                 me.resetEl = el.wrap({
27108                     cls: Ext.baseCSSPrefix + 'reset'
27109                 });
27110             }
27111         }
27112
27113         el.addCls(cls);
27114         el.setStyle(styles);
27115
27116         // Here we check if the component has a height set through style or css.
27117         // If it does then we set the this.height to that value and it won't be
27118         // considered an auto height component
27119         // if (this.height === undefined) {
27120         //     var height = el.getHeight();
27121         //     // This hopefully means that the panel has an explicit height set in style or css
27122         //     if (height - el.getPadding('tb') - el.getBorderWidth('tb') > 0) {
27123         //         this.height = height;
27124         //     }
27125         // }
27126
27127         me.el = el;
27128         
27129         me.rendered = true;
27130         me.addUIToElement(true);
27131         //loop through all exisiting uiCls and update the ui in them
27132         for (i = 0; i < me.uiCls.length; i++) {
27133             me.addUIClsToElement(me.uiCls[i], true);
27134         }
27135         me.rendered = false;
27136         me.initFrame();
27137
27138         renderTpl = me.initRenderTpl();
27139         if (renderTpl) {
27140             renderData = me.initRenderData();
27141             renderTpl.append(me.getTargetEl(), renderData);
27142         }
27143
27144         me.applyRenderSelectors();
27145         
27146         me.rendered = true;
27147         
27148         me.setUI(me.ui);
27149     },
27150
27151     // @private
27152     afterRender : function() {
27153         var me = this,
27154             pos,
27155             xy;
27156
27157         me.getComponentLayout();
27158
27159         // Set the size if a size is configured, or if this is the outermost Container
27160         if (!me.ownerCt || (me.height || me.width)) {
27161             me.setSize(me.width, me.height);
27162         }
27163
27164         // For floaters, calculate x and y if they aren't defined by aligning
27165         // the sized element to the center of either the the container or the ownerCt
27166         if (me.floating && (me.x === undefined || me.y === undefined)) {
27167             if (me.floatParent) {
27168                 xy = me.el.getAlignToXY(me.floatParent.getTargetEl(), 'c-c');
27169                 pos = me.floatParent.getTargetEl().translatePoints(xy[0], xy[1]);
27170             } else {
27171                 xy = me.el.getAlignToXY(me.container, 'c-c');
27172                 pos = me.container.translatePoints(xy[0], xy[1]);
27173             }
27174             me.x = me.x === undefined ? pos.left: me.x;
27175             me.y = me.y === undefined ? pos.top: me.y;
27176         }
27177
27178         if (Ext.isDefined(me.x) || Ext.isDefined(me.y)) {
27179             me.setPosition(me.x, me.y);
27180         }
27181
27182         if (me.styleHtmlContent) {
27183             me.getTargetEl().addCls(me.styleHtmlCls);
27184         }
27185     },
27186
27187     frameCls: Ext.baseCSSPrefix + 'frame',
27188
27189     frameTpl: [
27190         '<tpl if="top">',
27191             '<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>',
27192                 '<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>',
27193                     '<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>',
27194                 '<tpl if="right"></div></tpl>',
27195             '<tpl if="left"></div></tpl>',
27196         '</tpl>',
27197         '<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>',
27198             '<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>',
27199                 '<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>',
27200             '<tpl if="right"></div></tpl>',
27201         '<tpl if="left"></div></tpl>',
27202         '<tpl if="bottom">',
27203             '<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>',
27204                 '<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>',
27205                     '<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>',
27206                 '<tpl if="right"></div></tpl>',
27207             '<tpl if="left"></div></tpl>',
27208         '</tpl>'
27209     ],
27210
27211     frameTableTpl: [
27212         '<table><tbody>',
27213             '<tpl if="top">',
27214                 '<tr>',
27215                     '<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>',
27216                     '<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>',
27217                     '<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>',
27218                 '</tr>',
27219             '</tpl>',
27220             '<tr>',
27221                 '<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>',
27222                 '<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>',
27223                 '<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>',
27224             '</tr>',
27225             '<tpl if="bottom">',
27226                 '<tr>',
27227                     '<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>',
27228                     '<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>',
27229                     '<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>',
27230                 '</tr>',
27231             '</tpl>',
27232         '</tbody></table>'
27233     ],
27234     
27235     /**
27236      * @private
27237      */
27238     initFrame : function() {
27239         if (Ext.supports.CSS3BorderRadius) {
27240             return false;
27241         }
27242         
27243         var me = this,
27244             frameInfo = me.getFrameInfo(),
27245             frameWidth = frameInfo.width,
27246             frameTpl = me.getFrameTpl(frameInfo.table);
27247                         
27248         if (me.frame) {
27249             // Here we render the frameTpl to this component. This inserts the 9point div or the table framing.
27250             frameTpl.insertFirst(me.el, Ext.apply({}, {
27251                 ui:         me.ui,
27252                 uiCls:      me.uiCls,
27253                 frameCls:   me.frameCls,
27254                 baseCls:    me.baseCls,
27255                 frameWidth: frameWidth,
27256                 top:        !!frameInfo.top,
27257                 left:       !!frameInfo.left,
27258                 right:      !!frameInfo.right,
27259                 bottom:     !!frameInfo.bottom
27260             }, me.getFramePositions(frameInfo)));
27261
27262             // The frameBody is returned in getTargetEl, so that layouts render items to the correct target.=
27263             me.frameBody = me.el.down('.' + me.frameCls + '-mc');
27264             
27265             // Add the render selectors for each of the frame elements
27266             Ext.apply(me.renderSelectors, {
27267                 frameTL: '.' + me.baseCls + '-tl',
27268                 frameTC: '.' + me.baseCls + '-tc',
27269                 frameTR: '.' + me.baseCls + '-tr',
27270                 frameML: '.' + me.baseCls + '-ml',
27271                 frameMC: '.' + me.baseCls + '-mc',
27272                 frameMR: '.' + me.baseCls + '-mr',
27273                 frameBL: '.' + me.baseCls + '-bl',
27274                 frameBC: '.' + me.baseCls + '-bc',
27275                 frameBR: '.' + me.baseCls + '-br'
27276             });
27277         }
27278     },
27279     
27280     updateFrame: function() {
27281         if (Ext.supports.CSS3BorderRadius) {
27282             return false;
27283         }
27284         
27285         var me = this,
27286             wasTable = this.frameSize && this.frameSize.table,
27287             oldFrameTL = this.frameTL,
27288             oldFrameBL = this.frameBL,
27289             oldFrameML = this.frameML,
27290             oldFrameMC = this.frameMC,
27291             newMCClassName;
27292         
27293         this.initFrame();
27294         
27295         if (oldFrameMC) {
27296             if (me.frame) {                
27297                 // Reapply render selectors
27298                 delete me.frameTL;
27299                 delete me.frameTC;
27300                 delete me.frameTR;
27301                 delete me.frameML;
27302                 delete me.frameMC;
27303                 delete me.frameMR;
27304                 delete me.frameBL;
27305                 delete me.frameBC;
27306                 delete me.frameBR;    
27307                 this.applyRenderSelectors();
27308                 
27309                 // Store the class names set on the new mc
27310                 newMCClassName = this.frameMC.dom.className;
27311                 
27312                 // Replace the new mc with the old mc
27313                 oldFrameMC.insertAfter(this.frameMC);
27314                 this.frameMC.remove();
27315                 
27316                 // Restore the reference to the old frame mc as the framebody
27317                 this.frameBody = this.frameMC = oldFrameMC;
27318                 
27319                 // Apply the new mc classes to the old mc element
27320                 oldFrameMC.dom.className = newMCClassName;
27321                 
27322                 // Remove the old framing
27323                 if (wasTable) {
27324                     me.el.query('> table')[1].remove();
27325                 }                                
27326                 else {
27327                     if (oldFrameTL) {
27328                         oldFrameTL.remove();
27329                     }
27330                     if (oldFrameBL) {
27331                         oldFrameBL.remove();
27332                     }
27333                     oldFrameML.remove();
27334                 }
27335             }
27336             else {
27337                 // We were framed but not anymore. Move all content from the old frame to the body
27338                 
27339             }
27340         }
27341         else if (me.frame) {
27342             this.applyRenderSelectors();
27343         }
27344     },
27345     
27346     getFrameInfo: function() {
27347         if (Ext.supports.CSS3BorderRadius) {
27348             return false;
27349         }
27350         
27351         var me = this,
27352             left = me.el.getStyle('background-position-x'),
27353             top = me.el.getStyle('background-position-y'),
27354             info, frameInfo = false, max;
27355
27356         // Some browsers dont support background-position-x and y, so for those
27357         // browsers let's split background-position into two parts.
27358         if (!left && !top) {
27359             info = me.el.getStyle('background-position').split(' ');
27360             left = info[0];
27361             top = info[1];
27362         }
27363         
27364         // We actually pass a string in the form of '[type][tl][tr]px [type][br][bl]px' as
27365         // the background position of this.el from the css to indicate to IE that this component needs
27366         // framing. We parse it here and change the markup accordingly.
27367         if (parseInt(left, 10) >= 1000000 && parseInt(top, 10) >= 1000000) {
27368             max = Math.max;
27369             
27370             frameInfo = {
27371                 // Table markup starts with 110, div markup with 100.
27372                 table: left.substr(0, 3) == '110',
27373                 
27374                 // Determine if we are dealing with a horizontal or vertical component
27375                 vertical: top.substr(0, 3) == '110',
27376                 
27377                 // Get and parse the different border radius sizes
27378                 top:    max(left.substr(3, 2), left.substr(5, 2)),
27379                 right:  max(left.substr(5, 2), top.substr(3, 2)),
27380                 bottom: max(top.substr(3, 2), top.substr(5, 2)),
27381                 left:   max(top.substr(5, 2), left.substr(3, 2))
27382             };
27383             
27384             frameInfo.width = max(frameInfo.top, frameInfo.right, frameInfo.bottom, frameInfo.left);
27385
27386             // Just to be sure we set the background image of the el to none.
27387             me.el.setStyle('background-image', 'none');
27388         }        
27389         
27390         // This happens when you set frame: true explicitly without using the x-frame mixin in sass.
27391         // This way IE can't figure out what sizes to use and thus framing can't work.
27392         if (me.frame === true && !frameInfo) {
27393             Ext.Error.raise("You have set frame: true explicity on this component while it doesn't have any " +
27394                             "framing defined in the CSS template. In this case IE can't figure out what sizes " +
27395                             "to use and thus framing on this component will be disabled.");
27396         }
27397         
27398         me.frame = me.frame || !!frameInfo;
27399         me.frameSize = frameInfo || false;
27400         
27401         return frameInfo;
27402     },
27403     
27404     getFramePositions: function(frameInfo) {
27405         var me = this,
27406             frameWidth = frameInfo.width,
27407             dock = me.dock,
27408             positions, tc, bc, ml, mr;
27409             
27410         if (frameInfo.vertical) {
27411             tc = '0 -' + (frameWidth * 0) + 'px';
27412             bc = '0 -' + (frameWidth * 1) + 'px';
27413             
27414             if (dock && dock == "right") {
27415                 tc = 'right -' + (frameWidth * 0) + 'px';
27416                 bc = 'right -' + (frameWidth * 1) + 'px';
27417             }
27418             
27419             positions = {
27420                 tl: '0 -' + (frameWidth * 0) + 'px',
27421                 tr: '0 -' + (frameWidth * 1) + 'px',
27422                 bl: '0 -' + (frameWidth * 2) + 'px',
27423                 br: '0 -' + (frameWidth * 3) + 'px',
27424
27425                 ml: '-' + (frameWidth * 1) + 'px 0',
27426                 mr: 'right 0',
27427
27428                 tc: tc,
27429                 bc: bc
27430             };
27431         } else {
27432             ml = '-' + (frameWidth * 0) + 'px 0';
27433             mr = 'right 0';
27434             
27435             if (dock && dock == "bottom") {
27436                 ml = 'left bottom';
27437                 mr = 'right bottom';
27438             }
27439             
27440             positions = {
27441                 tl: '0 -' + (frameWidth * 2) + 'px',
27442                 tr: 'right -' + (frameWidth * 3) + 'px',
27443                 bl: '0 -' + (frameWidth * 4) + 'px',
27444                 br: 'right -' + (frameWidth * 5) + 'px',
27445
27446                 ml: ml,
27447                 mr: mr,
27448
27449                 tc: '0 -' + (frameWidth * 0) + 'px',
27450                 bc: '0 -' + (frameWidth * 1) + 'px'
27451             };
27452         }
27453         
27454         return positions;
27455     },
27456     
27457     /**
27458      * @private
27459      */
27460     getFrameTpl : function(table) {
27461         return table ? this.getTpl('frameTableTpl') : this.getTpl('frameTpl');
27462     },
27463
27464     /**
27465      * <p>Creates an array of class names from the configurations to add to this Component's <code>el</code> on render.</p>
27466      * <p>Private, but (possibly) used by ComponentQuery for selection by class name if Component is not rendered.</p>
27467      * @return {Array} An array of class names with which the Component's element will be rendered.
27468      * @private
27469      */
27470     initCls: function() {
27471         var me = this,
27472             cls = [];
27473
27474         cls.push(me.baseCls);
27475
27476         if (Ext.isDefined(me.cmpCls)) {
27477             if (Ext.isDefined(Ext.global.console)) {
27478                 Ext.global.console.warn('Ext.Component: cmpCls has been deprecated. Please use componentCls.');
27479             }
27480             me.componentCls = me.cmpCls;
27481             delete me.cmpCls;
27482         }
27483
27484         if (me.componentCls) {
27485             cls.push(me.componentCls);
27486         } else {
27487             me.componentCls = me.baseCls;
27488         }
27489         if (me.cls) {
27490             cls.push(me.cls);
27491             delete me.cls;
27492         }
27493
27494         return cls.concat(me.additionalCls);
27495     },
27496     
27497     /**
27498      * Sets the UI for the component. This will remove any existing UIs on the component. It will also
27499      * loop through any uiCls set on the component and rename them so they include the new UI
27500      * @param {String} ui The new UI for the component
27501      */
27502     setUI: function(ui) {
27503         var me = this,
27504             oldUICls = Ext.Array.clone(me.uiCls),
27505             newUICls = [],
27506             cls,
27507             i;
27508         
27509         //loop through all exisiting uiCls and update the ui in them
27510         for (i = 0; i < oldUICls.length; i++) {
27511             cls = oldUICls[i];
27512             
27513             me.removeClsWithUI(cls);
27514             newUICls.push(cls);
27515         }
27516         
27517         //remove the UI from the element
27518         me.removeUIFromElement();
27519         
27520         //set the UI
27521         me.ui = ui;
27522         
27523         //add the new UI to the elemend
27524         me.addUIToElement();
27525         
27526         //loop through all exisiting uiCls and update the ui in them
27527         for (i = 0; i < newUICls.length; i++) {
27528             cls = newUICls[i];
27529             
27530             me.addClsWithUI(cls);
27531         }
27532     },
27533     
27534     /**
27535      * Adds a cls to the uiCls array, which will also call {@link #addUIClsToElement} and adds
27536      * to all elements of this component.
27537      * @param {String/Array} cls A string or an array of strings to add to the uiCls
27538      */
27539     addClsWithUI: function(cls) {
27540         var me = this,
27541             i;
27542         
27543         if (!Ext.isArray(cls)) {
27544             cls = [cls];
27545         }
27546         
27547         for (i = 0; i < cls.length; i++) {
27548             if (cls[i] && !me.hasUICls(cls[i])) {
27549                 me.uiCls = Ext.Array.clone(me.uiCls);
27550                 me.uiCls.push(cls[i]);
27551                 me.addUIClsToElement(cls[i]);
27552             }
27553         }
27554     },
27555     
27556     /**
27557      * Removes a cls to the uiCls array, which will also call {@link #removeUIClsToElement} and removes
27558      * it from all elements of this component.
27559      * @param {String/Array} cls A string or an array of strings to remove to the uiCls
27560      */
27561     removeClsWithUI: function(cls) {
27562         var me = this,
27563             i;
27564         
27565         if (!Ext.isArray(cls)) {
27566             cls = [cls];
27567         }
27568         
27569         for (i = 0; i < cls.length; i++) {
27570             if (cls[i] && me.hasUICls(cls[i])) {
27571                 me.uiCls = Ext.Array.remove(me.uiCls, cls[i]);
27572                 me.removeUIClsFromElement(cls[i]);
27573             }
27574         }
27575     },
27576     
27577     /**
27578      * Checks if there is currently a specified uiCls
27579      * @param {String} cls The cls to check
27580      */
27581     hasUICls: function(cls) {
27582         var me = this,
27583             uiCls = me.uiCls || [];
27584         
27585         return Ext.Array.contains(uiCls, cls);
27586     },
27587     
27588     /**
27589      * Method which adds a specified UI + uiCls to the components element.
27590      * Can be overridden to remove the UI from more than just the components element.
27591      * @param {String} ui The UI to remove from the element
27592      * @private
27593      */
27594     addUIClsToElement: function(cls, force) {
27595         var me = this;
27596         
27597         me.addCls(Ext.baseCSSPrefix + cls);
27598         me.addCls(me.baseCls + '-' + cls);
27599         me.addCls(me.baseCls + '-' + me.ui + '-' + cls);
27600         
27601         if (!force && me.rendered && me.frame && !Ext.supports.CSS3BorderRadius) {
27602             // define each element of the frame
27603             var els = ['tl', 'tc', 'tr', 'ml', 'mc', 'mr', 'bl', 'bc', 'br'],
27604                 i, el;
27605             
27606             // loop through each of them, and if they are defined add the ui
27607             for (i = 0; i < els.length; i++) {
27608                 el = me['frame' + els[i].toUpperCase()];
27609                 
27610                 if (el && el.dom) {
27611                     el.addCls(me.baseCls + '-' + me.ui + '-' + els[i]);
27612                     el.addCls(me.baseCls + '-' + me.ui + '-' + cls + '-' + els[i]);
27613                 }
27614             }
27615         }
27616     },
27617     
27618     /**
27619      * Method which removes a specified UI + uiCls from the components element.
27620      * The cls which is added to the element will be: `this.baseCls + '-' + ui`
27621      * @param {String} ui The UI to add to the element
27622      * @private
27623      */
27624     removeUIClsFromElement: function(cls, force) {
27625         var me = this;
27626         
27627         me.removeCls(Ext.baseCSSPrefix + cls);
27628         me.removeCls(me.baseCls + '-' + cls);
27629         me.removeCls(me.baseCls + '-' + me.ui + '-' + cls);
27630         
27631         if (!force &&me.rendered && me.frame && !Ext.supports.CSS3BorderRadius) {
27632             // define each element of the frame
27633             var els = ['tl', 'tc', 'tr', 'ml', 'mc', 'mr', 'bl', 'bc', 'br'],
27634                 i, el;
27635             
27636             // loop through each of them, and if they are defined add the ui
27637             for (i = 0; i < els.length; i++) {
27638                 el = me['frame' + els[i].toUpperCase()];
27639                 if (el && el.dom) {
27640                     el.removeCls(me.baseCls + '-' + me.ui + '-' + cls + '-' + els[i]);
27641                 }
27642             }
27643         }
27644     },
27645     
27646     /**
27647      * Method which adds a specified UI to the components element.
27648      * @private
27649      */
27650     addUIToElement: function(force) {
27651         var me = this;
27652         
27653         me.addCls(me.baseCls + '-' + me.ui);
27654         
27655         if (me.rendered && me.frame && !Ext.supports.CSS3BorderRadius) {
27656             // define each element of the frame
27657             var els = ['tl', 'tc', 'tr', 'ml', 'mc', 'mr', 'bl', 'bc', 'br'],
27658                 i, el;
27659             
27660             // loop through each of them, and if they are defined add the ui
27661             for (i = 0; i < els.length; i++) {
27662                 el = me['frame' + els[i].toUpperCase()];
27663                 
27664                 if (el) {
27665                     el.addCls(me.baseCls + '-' + me.ui + '-' + els[i]);
27666                 }
27667             }
27668         }
27669     },
27670     
27671     /**
27672      * Method which removes a specified UI from the components element.
27673      * @private
27674      */
27675     removeUIFromElement: function() {
27676         var me = this;
27677         
27678         me.removeCls(me.baseCls + '-' + me.ui);
27679         
27680         if (me.rendered && me.frame && !Ext.supports.CSS3BorderRadius) {
27681             // define each element of the frame
27682             var els = ['tl', 'tc', 'tr', 'ml', 'mc', 'mr', 'bl', 'bc', 'br'],
27683                 i, el;
27684             
27685             // loop through each of them, and if they are defined add the ui
27686             for (i = 0; i < els.length; i++) {
27687                 el = me['frame' + els[i].toUpperCase()];
27688                 if (el) {
27689                     el.removeCls(me.baseCls + '-' + me.ui + '-' + els[i]);
27690                 }
27691             }
27692         }
27693     },
27694     
27695     getElConfig : function() {
27696         var result = this.autoEl || {tag: 'div'};
27697         result.id = this.id;
27698         return result;
27699     },
27700
27701     /**
27702      * This function takes the position argument passed to onRender and returns a
27703      * DOM element that you can use in the insertBefore.
27704      * @param {String/Number/Element/HTMLElement} position Index, element id or element you want
27705      * to put this component before.
27706      * @return {HTMLElement} DOM element that you can use in the insertBefore
27707      */
27708     getInsertPosition: function(position) {
27709         // Convert the position to an element to insert before
27710         if (position !== undefined) {
27711             if (Ext.isNumber(position)) {
27712                 position = this.container.dom.childNodes[position];
27713             }
27714             else {
27715                 position = Ext.getDom(position);
27716             }
27717         }
27718
27719         return position;
27720     },
27721
27722     /**
27723      * Adds ctCls to container.
27724      * @return {Ext.core.Element} The initialized container
27725      * @private
27726      */
27727     initContainer: function(container) {
27728         var me = this;
27729
27730         // If you render a component specifying the el, we get the container
27731         // of the el, and make sure we dont move the el around in the dom
27732         // during the render
27733         if (!container && me.el) {
27734             container = me.el.dom.parentNode;
27735             me.allowDomMove = false;
27736         }
27737
27738         me.container = Ext.get(container);
27739
27740         if (me.ctCls) {
27741             me.container.addCls(me.ctCls);
27742         }
27743
27744         return me.container;
27745     },
27746
27747     /**
27748      * Initialized the renderData to be used when rendering the renderTpl.
27749      * @return {Object} Object with keys and values that are going to be applied to the renderTpl
27750      * @private
27751      */
27752     initRenderData: function() {
27753         var me = this;
27754
27755         return Ext.applyIf(me.renderData, {
27756             ui: me.ui,
27757             uiCls: me.uiCls,
27758             baseCls: me.baseCls,
27759             componentCls: me.componentCls,
27760             frame: me.frame
27761         });
27762     },
27763
27764     /**
27765      * @private
27766      */
27767     getTpl: function(name) {
27768         var prototype = this.self.prototype,
27769             ownerPrototype;
27770
27771         if (this.hasOwnProperty(name)) {
27772             if (!(this[name] instanceof Ext.XTemplate)) {
27773                 this[name] = Ext.ClassManager.dynInstantiate('Ext.XTemplate', this[name]);
27774             }
27775
27776             return this[name];
27777         }
27778
27779         if (!(prototype[name] instanceof Ext.XTemplate)) {
27780             ownerPrototype = prototype;
27781
27782             do {
27783                 if (ownerPrototype.hasOwnProperty(name)) {
27784                     ownerPrototype[name] = Ext.ClassManager.dynInstantiate('Ext.XTemplate', ownerPrototype[name]);
27785                     break;
27786                 }
27787
27788                 ownerPrototype = ownerPrototype.superclass;
27789             } while (ownerPrototype);
27790         }
27791
27792         return prototype[name];
27793     },
27794
27795     /**
27796      * Initializes the renderTpl.
27797      * @return {Ext.XTemplate} The renderTpl XTemplate instance.
27798      * @private
27799      */
27800     initRenderTpl: function() {
27801         return this.getTpl('renderTpl');
27802     },
27803
27804     /**
27805      * Function description
27806      * @return {String} A CSS style string with style, padding, margin and border.
27807      * @private
27808      */
27809     initStyles: function() {
27810         var style = {},
27811             me = this,
27812             Element = Ext.core.Element;
27813
27814         if (Ext.isString(me.style)) {
27815             style = Element.parseStyles(me.style);
27816         } else {
27817             style = Ext.apply({}, me.style);
27818         }
27819
27820         // Convert the padding, margin and border properties from a space seperated string
27821         // into a proper style string
27822         if (me.padding !== undefined) {
27823             style.padding = Element.unitizeBox((me.padding === true) ? 5 : me.padding);
27824         }
27825
27826         if (me.margin !== undefined) {
27827             style.margin = Element.unitizeBox((me.margin === true) ? 5 : me.margin);
27828         }
27829
27830         delete me.style;
27831         return style;
27832     },
27833
27834     /**
27835      * Initializes this components contents. It checks for the properties
27836      * html, contentEl and tpl/data.
27837      * @private
27838      */
27839     initContent: function() {
27840         var me = this,
27841             target = me.getTargetEl(),
27842             contentEl,
27843             pre;
27844
27845         if (me.html) {
27846             target.update(Ext.core.DomHelper.markup(me.html));
27847             delete me.html;
27848         }
27849
27850         if (me.contentEl) {
27851             contentEl = Ext.get(me.contentEl);
27852             pre = Ext.baseCSSPrefix;
27853             contentEl.removeCls([pre + 'hidden', pre + 'hide-display', pre + 'hide-offsets', pre + 'hide-nosize']);
27854             target.appendChild(contentEl.dom);
27855         }
27856
27857         if (me.tpl) {
27858             // Make sure this.tpl is an instantiated XTemplate
27859             if (!me.tpl.isTemplate) {
27860                 me.tpl = Ext.create('Ext.XTemplate', me.tpl);
27861             }
27862
27863             if (me.data) {
27864                 me.tpl[me.tplWriteMode](target, me.data);
27865                 delete me.data;
27866             }
27867         }
27868     },
27869
27870     // @private
27871     initEvents : function() {
27872         var me = this,
27873             afterRenderEvents = me.afterRenderEvents,
27874             property, listeners;
27875         if (afterRenderEvents) {
27876             for (property in afterRenderEvents) {
27877                 if (afterRenderEvents.hasOwnProperty(property)) {
27878                     listeners = afterRenderEvents[property];
27879                     if (me[property] && me[property].on) {
27880                         me.mon(me[property], listeners);
27881                     }
27882                 }
27883             }
27884         }
27885     },
27886
27887     /**
27888      * Sets references to elements inside the component. E.g body -> x-panel-body
27889      * @private
27890      */
27891     applyRenderSelectors: function() {
27892         var selectors = this.renderSelectors || {},
27893             el = this.el.dom,
27894             selector;
27895
27896         for (selector in selectors) {
27897             if (selectors.hasOwnProperty(selector) && selectors[selector]) {
27898                 this[selector] = Ext.get(Ext.DomQuery.selectNode(selectors[selector], el));
27899             }
27900         }
27901     },
27902
27903     /**
27904      * Tests whether this Component matches the selector string.
27905      * @param {String} selector The selector string to test against.
27906      * @return {Boolean} True if this Component matches the selector.
27907      */
27908     is: function(selector) {
27909         return Ext.ComponentQuery.is(this, selector);
27910     },
27911
27912     /**
27913      * <p>Walks up the <code>ownerCt</code> axis looking for an ancestor Container which matches
27914      * the passed simple selector.</p>
27915      * <p>Example:<pre><code>
27916 var owningTabPanel = grid.up('tabpanel');
27917 </code></pre>
27918      * @param {String} selector Optional. The simple selector to test.
27919      * @return {Container} The matching ancestor Container (or <code>undefined</code> if no match was found).
27920      */
27921     up: function(selector) {
27922         var result = this.ownerCt;
27923         if (selector) {
27924             for (; result; result = result.ownerCt) {
27925                 if (Ext.ComponentQuery.is(result, selector)) {
27926                     return result;
27927                 }
27928             }
27929         }
27930         return result;
27931     },
27932
27933     /**
27934      * <p>Returns the next sibling of this Component.</p>
27935      * <p>Optionally selects the next sibling which matches the passed {@link Ext.ComponentQuery ComponentQuery} selector.</p>
27936      * <p>May also be refered to as <code><b>next()</b></code></p>
27937      * <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>
27938      * @param {String} selector Optional A {@link Ext.ComponentQuery ComponentQuery} selector to filter the following items.
27939      * @returns The next sibling (or the next sibling which matches the selector). Returns null if there is no matching sibling.
27940      */
27941     nextSibling: function(selector) {
27942         var o = this.ownerCt, it, last, idx, c;
27943         if (o) {
27944             it = o.items;
27945             idx = it.indexOf(this) + 1;
27946             if (idx) {
27947                 if (selector) {
27948                     for (last = it.getCount(); idx < last; idx++) {
27949                         if ((c = it.getAt(idx)).is(selector)) {
27950                             return c;
27951                         }
27952                     }
27953                 } else {
27954                     if (idx < it.getCount()) {
27955                         return it.getAt(idx);
27956                     }
27957                 }
27958             }
27959         }
27960         return null;
27961     },
27962
27963     /**
27964      * <p>Returns the previous sibling of this Component.</p>
27965      * <p>Optionally selects the previous sibling which matches the passed {@link Ext.ComponentQuery ComponentQuery} selector.</p>
27966      * <p>May also be refered to as <code><b>prev()</b></code></p>
27967      * <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>
27968      * @param {String} selector Optional. A {@link Ext.ComponentQuery ComponentQuery} selector to filter the preceding items.
27969      * @returns The previous sibling (or the previous sibling which matches the selector). Returns null if there is no matching sibling.
27970      */
27971     previousSibling: function(selector) {
27972         var o = this.ownerCt, it, idx, c;
27973         if (o) {
27974             it = o.items;
27975             idx = it.indexOf(this);
27976             if (idx != -1) {
27977                 if (selector) {
27978                     for (--idx; idx >= 0; idx--) {
27979                         if ((c = it.getAt(idx)).is(selector)) {
27980                             return c;
27981                         }
27982                     }
27983                 } else {
27984                     if (idx) {
27985                         return it.getAt(--idx);
27986                     }
27987                 }
27988             }
27989         }
27990         return null;
27991     },
27992
27993     /**
27994      * <p>Returns the previous node in the Component tree in tree traversal order.</p>
27995      * <p>Note that this is not limited to siblings, and if invoked upon a node with no matching siblings, will
27996      * walk the tree in reverse order to attempt to find a match. Contrast with {@link #previousSibling}.</p>
27997      * @param {String} selector Optional. A {@link Ext.ComponentQuery ComponentQuery} selector to filter the preceding nodes.
27998      * @returns The previous node (or the previous node which matches the selector). Returns null if there is no matching node.
27999      */
28000     previousNode: function(selector, includeSelf) {
28001         var node = this,
28002             result,
28003             it, len, i;
28004
28005         // If asked to include self, test me
28006         if (includeSelf && node.is(selector)) {
28007             return node;
28008         }
28009
28010         result = this.prev(selector);
28011         if (result) {
28012             return result;
28013         }
28014
28015         if (node.ownerCt) {
28016             for (it = node.ownerCt.items.items, i = Ext.Array.indexOf(it, node) - 1; i > -1; i--) {
28017                 if (it[i].query) {
28018                     result = it[i].query(selector);
28019                     result = result[result.length - 1];
28020                     if (result) {
28021                         return result;
28022                     }
28023                 }
28024             }
28025             return node.ownerCt.previousNode(selector, true);
28026         }
28027     },
28028
28029     /**
28030      * <p>Returns the next node in the Component tree in tree traversal order.</p>
28031      * <p>Note that this is not limited to siblings, and if invoked upon a node with no matching siblings, will
28032      * walk the tree to attempt to find a match. Contrast with {@link #pnextSibling}.</p>
28033      * @param {String} selector Optional A {@link Ext.ComponentQuery ComponentQuery} selector to filter the following nodes.
28034      * @returns The next node (or the next node which matches the selector). Returns null if there is no matching node.
28035      */
28036     nextNode: function(selector, includeSelf) {
28037         var node = this,
28038             result,
28039             it, len, i;
28040
28041         // If asked to include self, test me
28042         if (includeSelf && node.is(selector)) {
28043             return node;
28044         }
28045
28046         result = this.next(selector);
28047         if (result) {
28048             return result;
28049         }
28050
28051         if (node.ownerCt) {
28052             for (it = node.ownerCt.items, i = it.indexOf(node) + 1, it = it.items, len = it.length; i < len; i++) {
28053                 if (it[i].down) {
28054                     result = it[i].down(selector);
28055                     if (result) {
28056                         return result;
28057                     }
28058                 }
28059             }
28060             return node.ownerCt.nextNode(selector);
28061         }
28062     },
28063
28064     /**
28065      * Retrieves the id of this component.
28066      * Will autogenerate an id if one has not already been set.
28067      */
28068     getId : function() {
28069         return this.id || (this.id = 'ext-comp-' + (this.getAutoId()));
28070     },
28071
28072     getItemId : function() {
28073         return this.itemId || this.id;
28074     },
28075
28076     /**
28077      * Retrieves the top level element representing this component.
28078      */
28079     getEl : function() {
28080         return this.el;
28081     },
28082
28083     /**
28084      * This is used to determine where to insert the 'html', 'contentEl' and 'items' in this component.
28085      * @private
28086      */
28087     getTargetEl: function() {
28088         return this.frameBody || this.el;
28089     },
28090
28091     /**
28092      * <p>Tests whether or not this Component is of a specific xtype. This can test whether this Component is descended
28093      * from the xtype (default) or whether it is directly of the xtype specified (shallow = true).</p>
28094      * <p><b>If using your own subclasses, be aware that a Component must register its own xtype
28095      * to participate in determination of inherited xtypes.</b></p>
28096      * <p>For a list of all available xtypes, see the {@link Ext.Component} header.</p>
28097      * <p>Example usage:</p>
28098      * <pre><code>
28099 var t = new Ext.form.field.Text();
28100 var isText = t.isXType('textfield');        // true
28101 var isBoxSubclass = t.isXType('field');       // true, descended from Ext.form.field.Base
28102 var isBoxInstance = t.isXType('field', true); // false, not a direct Ext.form.field.Base instance
28103 </code></pre>
28104      * @param {String} xtype The xtype to check for this Component
28105      * @param {Boolean} shallow (optional) False to check whether this Component is descended from the xtype (this is
28106      * the default), or true to check whether this Component is directly of the specified xtype.
28107      * @return {Boolean} True if this component descends from the specified xtype, false otherwise.
28108      */
28109     isXType: function(xtype, shallow) {
28110         //assume a string by default
28111         if (Ext.isFunction(xtype)) {
28112             xtype = xtype.xtype;
28113             //handle being passed the class, e.g. Ext.Component
28114         } else if (Ext.isObject(xtype)) {
28115             xtype = xtype.statics().xtype;
28116             //handle being passed an instance
28117         }
28118
28119         return !shallow ? ('/' + this.getXTypes() + '/').indexOf('/' + xtype + '/') != -1: this.self.xtype == xtype;
28120     },
28121
28122     /**
28123      * <p>Returns this Component's xtype hierarchy as a slash-delimited string. For a list of all
28124      * available xtypes, see the {@link Ext.Component} header.</p>
28125      * <p><b>If using your own subclasses, be aware that a Component must register its own xtype
28126      * to participate in determination of inherited xtypes.</b></p>
28127      * <p>Example usage:</p>
28128      * <pre><code>
28129 var t = new Ext.form.field.Text();
28130 alert(t.getXTypes());  // alerts 'component/field/textfield'
28131 </code></pre>
28132      * @return {String} The xtype hierarchy string
28133      */
28134     getXTypes: function() {
28135         var self = this.self,
28136             xtypes      = [],
28137             parentPrototype  = this,
28138             xtype;
28139
28140         if (!self.xtypes) {
28141             while (parentPrototype && Ext.getClass(parentPrototype)) {
28142                 xtype = Ext.getClass(parentPrototype).xtype;
28143
28144                 if (xtype !== undefined) {
28145                     xtypes.unshift(xtype);
28146                 }
28147
28148                 parentPrototype = parentPrototype.superclass;
28149             }
28150
28151             self.xtypeChain = xtypes;
28152             self.xtypes = xtypes.join('/');
28153         }
28154
28155         return self.xtypes;
28156     },
28157
28158     /**
28159      * Update the content area of a component.
28160      * @param {Mixed} htmlOrData
28161      * If this component has been configured with a template via the tpl config
28162      * then it will use this argument as data to populate the template.
28163      * If this component was not configured with a template, the components
28164      * content area will be updated via Ext.core.Element update
28165      * @param {Boolean} loadScripts
28166      * (optional) Only legitimate when using the html configuration. Defaults to false
28167      * @param {Function} callback
28168      * (optional) Only legitimate when using the html configuration. Callback to execute when scripts have finished loading
28169      */
28170     update : function(htmlOrData, loadScripts, cb) {
28171         var me = this;
28172
28173         if (me.tpl && !Ext.isString(htmlOrData)) {
28174             me.data = htmlOrData;
28175             if (me.rendered) {
28176                 me.tpl[me.tplWriteMode](me.getTargetEl(), htmlOrData || {});
28177             }
28178         } else {
28179             me.html = Ext.isObject(htmlOrData) ? Ext.core.DomHelper.markup(htmlOrData) : htmlOrData;
28180             if (me.rendered) {
28181                 me.getTargetEl().update(me.html, loadScripts, cb);
28182             }
28183         }
28184
28185         if (me.rendered) {
28186             me.doComponentLayout();
28187         }
28188     },
28189
28190     /**
28191      * Convenience function to hide or show this component by boolean.
28192      * @param {Boolean} visible True to show, false to hide
28193      * @return {Ext.Component} this
28194      */
28195     setVisible : function(visible) {
28196         return this[visible ? 'show': 'hide']();
28197     },
28198
28199     /**
28200      * Returns true if this component is visible.
28201      * @param {Boolean} deep. <p>Optional. Pass <code>true</code> to interrogate the visibility status of all
28202      * parent Containers to determine whether this Component is truly visible to the user.</p>
28203      * <p>Generally, to determine whether a Component is hidden, the no argument form is needed. For example
28204      * when creating dynamically laid out UIs in a hidden Container before showing them.</p>
28205      * @return {Boolean} True if this component is visible, false otherwise.
28206      */
28207     isVisible: function(deep) {
28208         var me = this,
28209             child = me,
28210             visible = !me.hidden,
28211             ancestor = me.ownerCt;
28212
28213         // Clear hiddenOwnerCt property
28214         me.hiddenAncestor = false;
28215         if (me.destroyed) {
28216             return false;
28217         }
28218
28219         if (deep && visible && me.rendered && ancestor) {
28220             while (ancestor) {
28221                 // If any ancestor is hidden, then this is hidden.
28222                 // If an ancestor Panel (only Panels have a collapse method) is collapsed,
28223                 // then its layoutTarget (body) is hidden, so this is hidden unless its within a
28224                 // docked item; they are still visible when collapsed (Unless they themseves are hidden)
28225                 if (ancestor.hidden || (ancestor.collapsed &&
28226                         !(ancestor.getDockedItems && Ext.Array.contains(ancestor.getDockedItems(), child)))) {
28227                     // Store hiddenOwnerCt property if needed
28228                     me.hiddenAncestor = ancestor;
28229                     visible = false;
28230                     break;
28231                 }
28232                 child = ancestor;
28233                 ancestor = ancestor.ownerCt;
28234             }
28235         }
28236         return visible;
28237     },
28238
28239     /**
28240      * Enable the component
28241      * @param {Boolean} silent
28242      * Passing false will supress the 'enable' event from being fired.
28243      */
28244     enable: function(silent) {
28245         var me = this;
28246
28247         if (me.rendered) {
28248             me.el.removeCls(me.disabledCls);
28249             me.el.dom.disabled = false;
28250             me.onEnable();
28251         }
28252
28253         me.disabled = false;
28254
28255         if (silent !== true) {
28256             me.fireEvent('enable', me);
28257         }
28258
28259         return me;
28260     },
28261
28262     /**
28263      * Disable the component.
28264      * @param {Boolean} silent
28265      * Passing true, will supress the 'disable' event from being fired.
28266      */
28267     disable: function(silent) {
28268         var me = this;
28269
28270         if (me.rendered) {
28271             me.el.addCls(me.disabledCls);
28272             me.el.dom.disabled = true;
28273             me.onDisable();
28274         }
28275
28276         me.disabled = true;
28277
28278         if (silent !== true) {
28279             me.fireEvent('disable', me);
28280         }
28281
28282         return me;
28283     },
28284     
28285     // @private
28286     onEnable: function() {
28287         if (this.maskOnDisable) {
28288             this.el.unmask();
28289         }        
28290     },
28291
28292     // @private
28293     onDisable : function() {
28294         if (this.maskOnDisable) {
28295             this.el.mask();
28296         }
28297     },
28298     
28299     /**
28300      * Method to determine whether this Component is currently disabled.
28301      * @return {Boolean} the disabled state of this Component.
28302      */
28303     isDisabled : function() {
28304         return this.disabled;
28305     },
28306
28307     /**
28308      * Enable or disable the component.
28309      * @param {Boolean} disabled
28310      */
28311     setDisabled : function(disabled) {
28312         return this[disabled ? 'disable': 'enable']();
28313     },
28314
28315     /**
28316      * Method to determine whether this Component is currently set to hidden.
28317      * @return {Boolean} the hidden state of this Component.
28318      */
28319     isHidden : function() {
28320         return this.hidden;
28321     },
28322
28323     /**
28324      * Adds a CSS class to the top level element representing this component.
28325      * @param {String} cls The CSS class name to add
28326      * @return {Ext.Component} Returns the Component to allow method chaining.
28327      */
28328     addCls : function(className) {
28329         var me = this;
28330         if (!className) {
28331             return me;
28332         }
28333         if (!Ext.isArray(className)){
28334             className = className.replace(me.trimRe, '').split(me.spacesRe);
28335         }
28336         if (me.rendered) {
28337             me.el.addCls(className);
28338         }
28339         else {
28340             me.additionalCls = Ext.Array.unique(me.additionalCls.concat(className));
28341         }
28342         return me;
28343     },
28344
28345     /**
28346      * @deprecated 4.0 Replaced by {link:#addCls}
28347      * Adds a CSS class to the top level element representing this component.
28348      * @param {String} cls The CSS class name to add
28349      * @return {Ext.Component} Returns the Component to allow method chaining.
28350      */
28351     addClass : function() {
28352         return this.addCls.apply(this, arguments);
28353     },
28354
28355     /**
28356      * Removes a CSS class from the top level element representing this component.
28357      * @returns {Ext.Component} Returns the Component to allow method chaining.
28358      */
28359     removeCls : function(className) {
28360         var me = this;
28361
28362         if (!className) {
28363             return me;
28364         }
28365         if (!Ext.isArray(className)){
28366             className = className.replace(me.trimRe, '').split(me.spacesRe);
28367         }
28368         if (me.rendered) {
28369             me.el.removeCls(className);
28370         }
28371         else if (me.additionalCls.length) {
28372             Ext.each(className, function(cls) {
28373                 Ext.Array.remove(me.additionalCls, cls);
28374             });
28375         }
28376         return me;
28377     },
28378
28379     removeClass : function() {
28380         if (Ext.isDefined(Ext.global.console)) {
28381             Ext.global.console.warn('Ext.Component: removeClass has been deprecated. Please use removeCls.');
28382         }
28383         return this.removeCls.apply(this, arguments);
28384     },
28385
28386     addOverCls: function() {
28387         var me = this;
28388         if (!me.disabled) {
28389             me.el.addCls(me.overCls);
28390         }
28391     },
28392
28393     removeOverCls: function() {
28394         this.el.removeCls(this.overCls);
28395     },
28396
28397     addListener : function(element, listeners, scope, options) {
28398         var me = this,
28399             fn,
28400             option;
28401
28402         if (Ext.isString(element) && (Ext.isObject(listeners) || options && options.element)) {
28403             if (options.element) {
28404                 fn = listeners;
28405
28406                 listeners = {};
28407                 listeners[element] = fn;
28408                 element = options.element;
28409                 if (scope) {
28410                     listeners.scope = scope;
28411                 }
28412
28413                 for (option in options) {
28414                     if (options.hasOwnProperty(option)) {
28415                         if (me.eventOptionsRe.test(option)) {
28416                             listeners[option] = options[option];
28417                         }
28418                     }
28419                 }
28420             }
28421
28422             // At this point we have a variable called element,
28423             // and a listeners object that can be passed to on
28424             if (me[element] && me[element].on) {
28425                 me.mon(me[element], listeners);
28426             } else {
28427                 me.afterRenderEvents = me.afterRenderEvents || {};
28428                 me.afterRenderEvents[element] = listeners;
28429             }
28430         }
28431
28432         return me.mixins.observable.addListener.apply(me, arguments);
28433     },
28434
28435     // @TODO: implement removelistener to support the dom event stuff
28436
28437     /**
28438      * Provides the link for Observable's fireEvent method to bubble up the ownership hierarchy.
28439      * @return {Ext.container.Container} the Container which owns this Component.
28440      */
28441     getBubbleTarget : function() {
28442         return this.ownerCt;
28443     },
28444
28445     /**
28446      * Method to determine whether this Component is floating.
28447      * @return {Boolean} the floating state of this component.
28448      */
28449     isFloating : function() {
28450         return this.floating;
28451     },
28452
28453     /**
28454      * Method to determine whether this Component is draggable.
28455      * @return {Boolean} the draggable state of this component.
28456      */
28457     isDraggable : function() {
28458         return !!this.draggable;
28459     },
28460
28461     /**
28462      * Method to determine whether this Component is droppable.
28463      * @return {Boolean} the droppable state of this component.
28464      */
28465     isDroppable : function() {
28466         return !!this.droppable;
28467     },
28468
28469     /**
28470      * @private
28471      * Method to manage awareness of when components are added to their
28472      * respective Container, firing an added event.
28473      * References are established at add time rather than at render time.
28474      * @param {Ext.container.Container} container Container which holds the component
28475      * @param {number} pos Position at which the component was added
28476      */
28477     onAdded : function(container, pos) {
28478         this.ownerCt = container;
28479         this.fireEvent('added', this, container, pos);
28480     },
28481
28482     /**
28483      * @private
28484      * Method to manage awareness of when components are removed from their
28485      * respective Container, firing an removed event. References are properly
28486      * cleaned up after removing a component from its owning container.
28487      */
28488     onRemoved : function() {
28489         var me = this;
28490
28491         me.fireEvent('removed', me, me.ownerCt);
28492         delete me.ownerCt;
28493     },
28494
28495     // @private
28496     beforeDestroy : Ext.emptyFn,
28497     // @private
28498     // @private
28499     onResize : Ext.emptyFn,
28500
28501     /**
28502      * Sets the width and height of this Component. This method fires the {@link #resize} event. This method can accept
28503      * either width and height as separate arguments, or you can pass a size object like <code>{width:10, height:20}</code>.
28504      * @param {Mixed} width The new width to set. This may be one of:<div class="mdetail-params"><ul>
28505      * <li>A Number specifying the new width in the {@link #getEl Element}'s {@link Ext.core.Element#defaultUnit}s (by default, pixels).</li>
28506      * <li>A String used to set the CSS width style.</li>
28507      * <li>A size object in the format <code>{width: widthValue, height: heightValue}</code>.</li>
28508      * <li><code>undefined</code> to leave the width unchanged.</li>
28509      * </ul></div>
28510      * @param {Mixed} height The new height to set (not required if a size object is passed as the first arg).
28511      * This may be one of:<div class="mdetail-params"><ul>
28512      * <li>A Number specifying the new height in the {@link #getEl Element}'s {@link Ext.core.Element#defaultUnit}s (by default, pixels).</li>
28513      * <li>A String used to set the CSS height style. Animation may <b>not</b> be used.</li>
28514      * <li><code>undefined</code> to leave the height unchanged.</li>
28515      * </ul></div>
28516      * @return {Ext.Component} this
28517      */
28518     setSize : function(width, height) {
28519         var me = this,
28520             layoutCollection;
28521
28522         // support for standard size objects
28523         if (Ext.isObject(width)) {
28524             height = width.height;
28525             width  = width.width;
28526         }
28527
28528         // Constrain within configured maxima
28529         if (Ext.isNumber(width)) {
28530             width = Ext.Number.constrain(width, me.minWidth, me.maxWidth);
28531         }
28532         if (Ext.isNumber(height)) {
28533             height = Ext.Number.constrain(height, me.minHeight, me.maxHeight);
28534         }
28535
28536         if (!me.rendered || !me.isVisible()) {
28537             // If an ownerCt is hidden, add my reference onto the layoutOnShow stack.  Set the needsLayout flag.
28538             if (me.hiddenAncestor) {
28539                 layoutCollection = me.hiddenAncestor.layoutOnShow;
28540                 layoutCollection.remove(me);
28541                 layoutCollection.add(me);
28542             }
28543             me.needsLayout = {
28544                 width: width,
28545                 height: height,
28546                 isSetSize: true
28547             };
28548             if (!me.rendered) {
28549                 me.width  = (width !== undefined) ? width : me.width;
28550                 me.height = (height !== undefined) ? height : me.height;
28551             }
28552             return me;
28553         }
28554         me.doComponentLayout(width, height, true);
28555
28556         return me;
28557     },
28558
28559     setCalculatedSize : function(width, height, ownerCt) {
28560         var me = this,
28561             layoutCollection;
28562
28563         // support for standard size objects
28564         if (Ext.isObject(width)) {
28565             ownerCt = width.ownerCt;
28566             height = width.height;
28567             width  = width.width;
28568         }
28569
28570         // Constrain within configured maxima
28571         if (Ext.isNumber(width)) {
28572             width = Ext.Number.constrain(width, me.minWidth, me.maxWidth);
28573         }
28574         if (Ext.isNumber(height)) {
28575             height = Ext.Number.constrain(height, me.minHeight, me.maxHeight);
28576         }
28577
28578         if (!me.rendered || !me.isVisible()) {
28579             // If an ownerCt is hidden, add my reference onto the layoutOnShow stack.  Set the needsLayout flag.
28580             if (me.hiddenAncestor) {
28581                 layoutCollection = me.hiddenAncestor.layoutOnShow;
28582                 layoutCollection.remove(me);
28583                 layoutCollection.add(me);
28584             }
28585             me.needsLayout = {
28586                 width: width,
28587                 height: height,
28588                 isSetSize: false,
28589                 ownerCt: ownerCt
28590             };
28591             return me;
28592         }
28593         me.doComponentLayout(width, height, false, ownerCt);
28594
28595         return me;
28596     },
28597
28598     /**
28599      * This method needs to be called whenever you change something on this component that requires the Component's
28600      * layout to be recalculated.
28601      * @return {Ext.container.Container} this
28602      */
28603     doComponentLayout : function(width, height, isSetSize, ownerCt) {
28604         var me = this,
28605             componentLayout = me.getComponentLayout();
28606
28607         // collapsed state is not relevant here, so no testing done.
28608         // Only Panels have a collapse method, and that just sets the width/height such that only
28609         // a single docked Header parallel to the collapseTo side are visible, and the Panel body is hidden.
28610         if (me.rendered && componentLayout) {
28611             width = (width !== undefined) ? width : me.width;
28612             height = (height !== undefined) ? height : me.height;
28613             if (isSetSize) {
28614                 me.width = width;
28615                 me.height = height;
28616             }
28617
28618             componentLayout.layout(width, height, isSetSize, ownerCt);
28619         }
28620         return me;
28621     },
28622
28623     // @private
28624     setComponentLayout : function(layout) {
28625         var currentLayout = this.componentLayout;
28626         if (currentLayout && currentLayout.isLayout && currentLayout != layout) {
28627             currentLayout.setOwner(null);
28628         }
28629         this.componentLayout = layout;
28630         layout.setOwner(this);
28631     },
28632
28633     getComponentLayout : function() {
28634         var me = this;
28635
28636         if (!me.componentLayout || !me.componentLayout.isLayout) {
28637             me.setComponentLayout(Ext.layout.Layout.create(me.componentLayout, 'autocomponent'));
28638         }
28639         return me.componentLayout;
28640     },
28641
28642     /**
28643      * @param {Number} adjWidth The box-adjusted width that was set
28644      * @param {Number} adjHeight The box-adjusted height that was set
28645      * @param {Boolean} isSetSize Whether or not the height/width are stored on the component permanently
28646      * @param {Ext.Component} layoutOwner Component which sent the layout. Only used when isSetSize is false.
28647      */
28648     afterComponentLayout: function(width, height, isSetSize, layoutOwner) {
28649         this.fireEvent('resize', this, width, height);
28650     },
28651
28652     /**
28653      * Occurs before componentLayout is run. Returning false from this method will prevent the componentLayout
28654      * from being executed.
28655      * @param {Number} adjWidth The box-adjusted width that was set
28656      * @param {Number} adjHeight The box-adjusted height that was set
28657      * @param {Boolean} isSetSize Whether or not the height/width are stored on the component permanently
28658      * @param {Ext.Component} layoutOwner Component which sent the layout. Only used when isSetSize is false.
28659      */
28660     beforeComponentLayout: function(width, height, isSetSize, layoutOwner) {
28661         return true;
28662     },
28663
28664     /**
28665      * Sets the left and top of the component.  To set the page XY position instead, use {@link #setPagePosition}.
28666      * This method fires the {@link #move} event.
28667      * @param {Number} left The new left
28668      * @param {Number} top The new top
28669      * @return {Ext.Component} this
28670      */
28671     setPosition : function(x, y) {
28672         var me = this;
28673
28674         if (Ext.isObject(x)) {
28675             y = x.y;
28676             x = x.x;
28677         }
28678
28679         if (!me.rendered) {
28680             return me;
28681         }
28682
28683         if (x !== undefined || y !== undefined) {
28684             me.el.setBox(x, y);
28685             me.onPosition(x, y);
28686             me.fireEvent('move', me, x, y);
28687         }
28688         return me;
28689     },
28690
28691     /* @private
28692      * Called after the component is moved, this method is empty by default but can be implemented by any
28693      * subclass that needs to perform custom logic after a move occurs.
28694      * @param {Number} x The new x position
28695      * @param {Number} y The new y position
28696      */
28697     onPosition: Ext.emptyFn,
28698
28699     /**
28700      * Sets the width of the component.  This method fires the {@link #resize} event.
28701      * @param {Number} width The new width to setThis may be one of:<div class="mdetail-params"><ul>
28702      * <li>A Number specifying the new width in the {@link #getEl Element}'s {@link Ext.core.Element#defaultUnit}s (by default, pixels).</li>
28703      * <li>A String used to set the CSS width style.</li>
28704      * </ul></div>
28705      * @return {Ext.Component} this
28706      */
28707     setWidth : function(width) {
28708         return this.setSize(width);
28709     },
28710
28711     /**
28712      * Sets the height of the component.  This method fires the {@link #resize} event.
28713      * @param {Number} height The new height to set. This may be one of:<div class="mdetail-params"><ul>
28714      * <li>A Number specifying the new height in the {@link #getEl Element}'s {@link Ext.core.Element#defaultUnit}s (by default, pixels).</li>
28715      * <li>A String used to set the CSS height style.</li>
28716      * <li><i>undefined</i> to leave the height unchanged.</li>
28717      * </ul></div>
28718      * @return {Ext.Component} this
28719      */
28720     setHeight : function(height) {
28721         return this.setSize(undefined, height);
28722     },
28723
28724     /**
28725      * Gets the current size of the component's underlying element.
28726      * @return {Object} An object containing the element's size {width: (element width), height: (element height)}
28727      */
28728     getSize : function() {
28729         return this.el.getSize();
28730     },
28731
28732     /**
28733      * Gets the current width of the component's underlying element.
28734      * @return {Number}
28735      */
28736     getWidth : function() {
28737         return this.el.getWidth();
28738     },
28739
28740     /**
28741      * Gets the current height of the component's underlying element.
28742      * @return {Number}
28743      */
28744     getHeight : function() {
28745         return this.el.getHeight();
28746     },
28747
28748     /**
28749      * Gets the {@link Ext.ComponentLoader} for this Component.
28750      * @return {Ext.ComponentLoader} The loader instance, null if it doesn't exist.
28751      */
28752     getLoader: function(){
28753         var me = this,
28754             autoLoad = me.autoLoad ? (Ext.isObject(me.autoLoad) ? me.autoLoad : {url: me.autoLoad}) : null,
28755             loader = me.loader || autoLoad;
28756
28757         if (loader) {
28758             if (!loader.isLoader) {
28759                 me.loader = Ext.create('Ext.ComponentLoader', Ext.apply({
28760                     target: me,
28761                     autoLoad: autoLoad
28762                 }, loader));
28763             } else {
28764                 loader.setTarget(me);
28765             }
28766             return me.loader;
28767
28768         }
28769         return null;
28770     },
28771
28772     /**
28773      * This method allows you to show or hide a LoadMask on top of this component.
28774      * @param {Boolean/Object/String} load True to show the default LoadMask, a config object
28775      * that will be passed to the LoadMask constructor, or a message String to show. False to
28776      * hide the current LoadMask.
28777      * @param {Boolean} targetEl True to mask the targetEl of this Component instead of the this.el.
28778      * For example, setting this to true on a Panel will cause only the body to be masked. (defaults to false)
28779      * @return {Ext.LoadMask} The LoadMask instance that has just been shown.
28780      */
28781     setLoading : function(load, targetEl) {
28782         var me = this,
28783             config;
28784
28785         if (me.rendered) {
28786             if (load !== false && !me.collapsed) {
28787                 if (Ext.isObject(load)) {
28788                     config = load;
28789                 }
28790                 else if (Ext.isString(load)) {
28791                     config = {msg: load};
28792                 }
28793                 else {
28794                     config = {};
28795                 }
28796                 me.loadMask = me.loadMask || Ext.create('Ext.LoadMask', targetEl ? me.getTargetEl() : me.el, config);
28797                 me.loadMask.show();
28798             } else if (me.loadMask) {
28799                 Ext.destroy(me.loadMask);
28800                 me.loadMask = null;
28801             }
28802         }
28803
28804         return me.loadMask;
28805     },
28806
28807     /**
28808      * Sets the dock position of this component in its parent panel. Note that
28809      * this only has effect if this item is part of the dockedItems collection
28810      * of a parent that has a DockLayout (note that any Panel has a DockLayout
28811      * by default)
28812      * @return {Component} this
28813      */
28814     setDocked : function(dock, layoutParent) {
28815         var me = this;
28816
28817         me.dock = dock;
28818         if (layoutParent && me.ownerCt && me.rendered) {
28819             me.ownerCt.doComponentLayout();
28820         }
28821         return me;
28822     },
28823
28824     onDestroy : function() {
28825         var me = this;
28826
28827         if (me.monitorResize && Ext.EventManager.resizeEvent) {
28828             Ext.EventManager.resizeEvent.removeListener(me.setSize, me);
28829         }
28830         Ext.destroy(me.componentLayout, me.loadMask);
28831     },
28832
28833     /**
28834      * Destroys the Component.
28835      */
28836     destroy : function() {
28837         var me = this;
28838
28839         if (!me.isDestroyed) {
28840             if (me.fireEvent('beforedestroy', me) !== false) {
28841                 me.destroying = true;
28842                 me.beforeDestroy();
28843
28844                 if (me.floating) {
28845                     delete me.floatParent;
28846                     // A zIndexManager is stamped into a *floating* Component when it is added to a Container.
28847                     // If it has no zIndexManager at render time, it is assigned to the global Ext.WindowManager instance.
28848                     if (me.zIndexManager) {
28849                         me.zIndexManager.unregister(me);
28850                     }
28851                 } else if (me.ownerCt && me.ownerCt.remove) {
28852                     me.ownerCt.remove(me, false);
28853                 }
28854
28855                 if (me.rendered) {
28856                     me.el.remove();
28857                 }
28858
28859                 me.onDestroy();
28860
28861                 // Attempt to destroy all plugins
28862                 Ext.destroy(me.plugins);
28863
28864                 Ext.ComponentManager.unregister(me);
28865                 me.fireEvent('destroy', me);
28866
28867                 me.mixins.state.destroy.call(me);
28868
28869                 me.clearListeners();
28870                 me.destroying = false;
28871                 me.isDestroyed = true;
28872             }
28873         }
28874     },
28875
28876     /**
28877      * Retrieves a plugin by its pluginId which has been bound to this
28878      * component.
28879      * @returns {Ext.AbstractPlugin} pluginInstance
28880      */
28881     getPlugin: function(pluginId) {
28882         var i = 0,
28883             plugins = this.plugins,
28884             ln = plugins.length;
28885         for (; i < ln; i++) {
28886             if (plugins[i].pluginId === pluginId) {
28887                 return plugins[i];
28888             }
28889         }
28890     },
28891     
28892     /**
28893      * Determines whether this component is the descendant of a particular container.
28894      * @param {Ext.Container} container
28895      * @returns {Boolean} isDescendant
28896      */
28897     isDescendantOf: function(container) {
28898         return !!this.findParentBy(function(p){
28899             return p === container;
28900         });
28901     }
28902 }, function() {
28903     this.createAlias({
28904         on: 'addListener',
28905         prev: 'previousSibling',
28906         next: 'nextSibling'
28907     });
28908 });
28909
28910 /**
28911  * @class Ext.AbstractPlugin
28912  * @extends Object
28913  *
28914  * Plugins are injected 
28915  */
28916 Ext.define('Ext.AbstractPlugin', {
28917     disabled: false,
28918     
28919     constructor: function(config) {
28920         if (!config.cmp && Ext.global.console) {
28921             Ext.global.console.warn("Attempted to attach a plugin ");
28922         }
28923         Ext.apply(this, config);
28924     },
28925     
28926     getCmp: function() {
28927         return this.cmp;
28928     },
28929
28930     /**
28931      * The init method is invoked after initComponent has been run for the
28932      * component which we are injecting the plugin into.
28933      */
28934     init: Ext.emptyFn,
28935
28936     /**
28937      * The destroy method is invoked by the owning Component at the time the Component is being destroyed.
28938      * Use this method to clean up an resources.
28939      */
28940     destroy: Ext.emptyFn,
28941
28942     /**
28943      * Enable the plugin and set the disabled flag to false.
28944      */
28945     enable: function() {
28946         this.disabled = false;
28947     },
28948
28949     /**
28950      * Disable the plugin and set the disabled flag to true.
28951      */
28952     disable: function() {
28953         this.disabled = true;
28954     }
28955 });
28956
28957 /**
28958  * @class Ext.data.Connection
28959  * The Connection class encapsulates a connection to the page's originating domain, allowing requests to be made either
28960  * to a configured URL, or to a URL specified at request time.
28961  *
28962  * Requests made by this class are asynchronous, and will return immediately. No data from the server will be available
28963  * to the statement immediately following the {@link #request} call. To process returned data, use a success callback
28964  * in the request options object, or an {@link #requestcomplete event listener}.
28965  *
28966  * <p><u>File Uploads</u></p>
28967  *
28968  * File uploads are not performed using normal "Ajax" techniques, that is they are not performed using XMLHttpRequests.
28969  * Instead the form is submitted in the standard manner with the DOM &lt;form&gt; element temporarily modified to have its
28970  * target set to refer to a dynamically generated, hidden &lt;iframe&gt; which is inserted into the document but removed
28971  * after the return data has been gathered.
28972  *
28973  * The server response is parsed by the browser to create the document for the IFRAME. If the server is using JSON to
28974  * send the return object, then the Content-Type header must be set to "text/html" in order to tell the browser to
28975  * insert the text unchanged into the document body.
28976  *
28977  * Characters which are significant to an HTML parser must be sent as HTML entities, so encode "&lt;" as "&amp;lt;", "&amp;" as
28978  * "&amp;amp;" etc.
28979  *
28980  * The response text is retrieved from the document, and a fake XMLHttpRequest object is created containing a
28981  * responseText property in order to conform to the requirements of event handlers and callbacks.
28982  *
28983  * Be aware that file upload packets are sent with the content type multipart/form and some server technologies
28984  * (notably JEE) may require some custom processing in order to retrieve parameter names and parameter values from the
28985  * packet content.
28986  *
28987  * Also note that it's not possible to check the response code of the hidden iframe, so the success handler will ALWAYS fire.
28988  */
28989 Ext.define('Ext.data.Connection', {
28990     mixins: {
28991         observable: 'Ext.util.Observable'
28992     },
28993
28994     statics: {
28995         requestId: 0
28996     },
28997
28998     url: null,
28999     async: true,
29000     method: null,
29001     username: '',
29002     password: '',
29003
29004     /**
29005      * @cfg {Boolean} disableCaching (Optional) True to add a unique cache-buster param to GET requests. (defaults to true)
29006      * @type Boolean
29007      */
29008     disableCaching: true,
29009
29010     /**
29011      * @cfg {String} disableCachingParam (Optional) Change the parameter which is sent went disabling caching
29012      * through a cache buster. Defaults to '_dc'
29013      * @type String
29014      */
29015     disableCachingParam: '_dc',
29016
29017     /**
29018      * @cfg {Number} timeout (Optional) The timeout in milliseconds to be used for requests. (defaults to 30000)
29019      */
29020     timeout : 30000,
29021
29022     /**
29023      * @param {Object} extraParams (Optional) Any parameters to be appended to the request.
29024      */
29025
29026     useDefaultHeader : true,
29027     defaultPostHeader : 'application/x-www-form-urlencoded; charset=UTF-8',
29028     useDefaultXhrHeader : true,
29029     defaultXhrHeader : 'XMLHttpRequest',
29030
29031     constructor : function(config) {
29032         config = config || {};
29033         Ext.apply(this, config);
29034
29035         this.addEvents(
29036             /**
29037              * @event beforerequest
29038              * Fires before a network request is made to retrieve a data object.
29039              * @param {Connection} conn This Connection object.
29040              * @param {Object} options The options config object passed to the {@link #request} method.
29041              */
29042             'beforerequest',
29043             /**
29044              * @event requestcomplete
29045              * Fires if the request was successfully completed.
29046              * @param {Connection} conn This Connection object.
29047              * @param {Object} response The XHR object containing the response data.
29048              * See <a href="http://www.w3.org/TR/XMLHttpRequest/">The XMLHttpRequest Object</a>
29049              * for details.
29050              * @param {Object} options The options config object passed to the {@link #request} method.
29051              */
29052             'requestcomplete',
29053             /**
29054              * @event requestexception
29055              * Fires if an error HTTP status was returned from the server.
29056              * See <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html">HTTP Status Code Definitions</a>
29057              * for details of HTTP status codes.
29058              * @param {Connection} conn This Connection object.
29059              * @param {Object} response The XHR object containing the response data.
29060              * See <a href="http://www.w3.org/TR/XMLHttpRequest/">The XMLHttpRequest Object</a>
29061              * for details.
29062              * @param {Object} options The options config object passed to the {@link #request} method.
29063              */
29064             'requestexception'
29065         );
29066         this.requests = {};
29067         this.mixins.observable.constructor.call(this);
29068     },
29069
29070     /**
29071      * <p>Sends an HTTP request to a remote server.</p>
29072      * <p><b>Important:</b> Ajax server requests are asynchronous, and this call will
29073      * return before the response has been received. Process any returned data
29074      * in a callback function.</p>
29075      * <pre><code>
29076 Ext.Ajax.request({
29077 url: 'ajax_demo/sample.json',
29078 success: function(response, opts) {
29079   var obj = Ext.decode(response.responseText);
29080   console.dir(obj);
29081 },
29082 failure: function(response, opts) {
29083   console.log('server-side failure with status code ' + response.status);
29084 }
29085 });
29086      * </code></pre>
29087      * <p>To execute a callback function in the correct scope, use the <tt>scope</tt> option.</p>
29088      * @param {Object} options An object which may contain the following properties:<ul>
29089      * <li><b>url</b> : String/Function (Optional)<div class="sub-desc">The URL to
29090      * which to send the request, or a function to call which returns a URL string. The scope of the
29091      * function is specified by the <tt>scope</tt> option. Defaults to the configured
29092      * <tt>{@link #url}</tt>.</div></li>
29093      * <li><b>params</b> : Object/String/Function (Optional)<div class="sub-desc">
29094      * An object containing properties which are used as parameters to the
29095      * request, a url encoded string or a function to call to get either. The scope of the function
29096      * is specified by the <tt>scope</tt> option.</div></li>
29097      * <li><b>method</b> : String (Optional)<div class="sub-desc">The HTTP method to use
29098      * for the request. Defaults to the configured method, or if no method was configured,
29099      * "GET" if no parameters are being sent, and "POST" if parameters are being sent.  Note that
29100      * the method name is case-sensitive and should be all caps.</div></li>
29101      * <li><b>callback</b> : Function (Optional)<div class="sub-desc">The
29102      * function to be called upon receipt of the HTTP response. The callback is
29103      * called regardless of success or failure and is passed the following
29104      * parameters:<ul>
29105      * <li><b>options</b> : Object<div class="sub-desc">The parameter to the request call.</div></li>
29106      * <li><b>success</b> : Boolean<div class="sub-desc">True if the request succeeded.</div></li>
29107      * <li><b>response</b> : Object<div class="sub-desc">The XMLHttpRequest object containing the response data.
29108      * See <a href="http://www.w3.org/TR/XMLHttpRequest/">http://www.w3.org/TR/XMLHttpRequest/</a> for details about
29109      * accessing elements of the response.</div></li>
29110      * </ul></div></li>
29111      * <li><a id="request-option-success"></a><b>success</b> : Function (Optional)<div class="sub-desc">The function
29112      * to be called upon success of the request. The callback is passed the following
29113      * parameters:<ul>
29114      * <li><b>response</b> : Object<div class="sub-desc">The XMLHttpRequest object containing the response data.</div></li>
29115      * <li><b>options</b> : Object<div class="sub-desc">The parameter to the request call.</div></li>
29116      * </ul></div></li>
29117      * <li><b>failure</b> : Function (Optional)<div class="sub-desc">The function
29118      * to be called upon failure of the request. The callback is passed the
29119      * following parameters:<ul>
29120      * <li><b>response</b> : Object<div class="sub-desc">The XMLHttpRequest object containing the response data.</div></li>
29121      * <li><b>options</b> : Object<div class="sub-desc">The parameter to the request call.</div></li>
29122      * </ul></div></li>
29123      * <li><b>scope</b> : Object (Optional)<div class="sub-desc">The scope in
29124      * which to execute the callbacks: The "this" object for the callback function. If the <tt>url</tt>, or <tt>params</tt> options were
29125      * specified as functions from which to draw values, then this also serves as the scope for those function calls.
29126      * Defaults to the browser window.</div></li>
29127      * <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>
29128      * <li><b>form</b> : Element/HTMLElement/String (Optional)<div class="sub-desc">The <tt>&lt;form&gt;</tt>
29129      * Element or the id of the <tt>&lt;form&gt;</tt> to pull parameters from.</div></li>
29130      * <li><a id="request-option-isUpload"></a><b>isUpload</b> : Boolean (Optional)<div class="sub-desc"><b>Only meaningful when used
29131      * with the <tt>form</tt> option</b>.
29132      * <p>True if the form object is a file upload (will be set automatically if the form was
29133      * configured with <b><tt>enctype</tt></b> "multipart/form-data").</p>
29134      * <p>File uploads are not performed using normal "Ajax" techniques, that is they are <b>not</b>
29135      * performed using XMLHttpRequests. Instead the form is submitted in the standard manner with the
29136      * DOM <tt>&lt;form></tt> element temporarily modified to have its
29137      * <a href="http://www.w3.org/TR/REC-html40/present/frames.html#adef-target">target</a> set to refer
29138      * to a dynamically generated, hidden <tt>&lt;iframe></tt> which is inserted into the document
29139      * but removed after the return data has been gathered.</p>
29140      * <p>The server response is parsed by the browser to create the document for the IFRAME. If the
29141      * server is using JSON to send the return object, then the
29142      * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17">Content-Type</a> header
29143      * must be set to "text/html" in order to tell the browser to insert the text unchanged into the document body.</p>
29144      * <p>The response text is retrieved from the document, and a fake XMLHttpRequest object
29145      * is created containing a <tt>responseText</tt> property in order to conform to the
29146      * requirements of event handlers and callbacks.</p>
29147      * <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>
29148      * and some server technologies (notably JEE) may require some custom processing in order to
29149      * retrieve parameter names and parameter values from the packet content.</p>
29150      * </div></li>
29151      * <li><b>headers</b> : Object (Optional)<div class="sub-desc">Request
29152      * headers to set for the request.</div></li>
29153      * <li><b>xmlData</b> : Object (Optional)<div class="sub-desc">XML document
29154      * to use for the post. Note: This will be used instead of params for the post
29155      * data. Any params will be appended to the URL.</div></li>
29156      * <li><b>jsonData</b> : Object/String (Optional)<div class="sub-desc">JSON
29157      * data to use as the post. Note: This will be used instead of params for the post
29158      * data. Any params will be appended to the URL.</div></li>
29159      * <li><b>disableCaching</b> : Boolean (Optional)<div class="sub-desc">True
29160      * to add a unique cache-buster param to GET requests.</div></li>
29161      * </ul></p>
29162      * <p>The options object may also contain any other property which might be needed to perform
29163      * postprocessing in a callback because it is passed to callback functions.</p>
29164      * @return {Object} request The request object. This may be used
29165      * to cancel the request.
29166      */
29167     request : function(options) {
29168         options = options || {};
29169         var me = this,
29170             scope = options.scope || window,
29171             username = options.username || me.username,
29172             password = options.password || me.password || '',
29173             async,
29174             requestOptions,
29175             request,
29176             headers,
29177             xhr;
29178
29179         if (me.fireEvent('beforerequest', me, options) !== false) {
29180
29181             requestOptions = me.setOptions(options, scope);
29182
29183             if (this.isFormUpload(options) === true) {
29184                 this.upload(options.form, requestOptions.url, requestOptions.data, options);
29185                 return null;
29186             }
29187
29188             // if autoabort is set, cancel the current transactions
29189             if (options.autoAbort === true || me.autoAbort) {
29190                 me.abort();
29191             }
29192
29193             // create a connection object
29194             xhr = this.getXhrInstance();
29195
29196             async = options.async !== false ? (options.async || me.async) : false;
29197
29198             // open the request
29199             if (username) {
29200                 xhr.open(requestOptions.method, requestOptions.url, async, username, password);
29201             } else {
29202                 xhr.open(requestOptions.method, requestOptions.url, async);
29203             }
29204
29205             headers = me.setupHeaders(xhr, options, requestOptions.data, requestOptions.params);
29206
29207             // create the transaction object
29208             request = {
29209                 id: ++Ext.data.Connection.requestId,
29210                 xhr: xhr,
29211                 headers: headers,
29212                 options: options,
29213                 async: async,
29214                 timeout: setTimeout(function() {
29215                     request.timedout = true;
29216                     me.abort(request);
29217                 }, options.timeout || me.timeout)
29218             };
29219             me.requests[request.id] = request;
29220
29221             // bind our statechange listener
29222             if (async) {
29223                 xhr.onreadystatechange = Ext.Function.bind(me.onStateChange, me, [request]);
29224             }
29225
29226             // start the request!
29227             xhr.send(requestOptions.data);
29228             if (!async) {
29229                 return this.onComplete(request);
29230             }
29231             return request;
29232         } else {
29233             Ext.callback(options.callback, options.scope, [options, undefined, undefined]);
29234             return null;
29235         }
29236     },
29237
29238     /**
29239      * Upload a form using a hidden iframe.
29240      * @param {Mixed} form The form to upload
29241      * @param {String} url The url to post to
29242      * @param {String} params Any extra parameters to pass
29243      * @param {Object} options The initial options
29244      */
29245     upload: function(form, url, params, options){
29246         form = Ext.getDom(form);
29247         options = options || {};
29248
29249         var id = Ext.id(),
29250                 frame = document.createElement('iframe'),
29251                 hiddens = [],
29252                 encoding = 'multipart/form-data',
29253                 buf = {
29254                     target: form.target,
29255                     method: form.method,
29256                     encoding: form.encoding,
29257                     enctype: form.enctype,
29258                     action: form.action
29259                 }, hiddenItem;
29260
29261         /*
29262          * Originally this behaviour was modified for Opera 10 to apply the secure URL after
29263          * the frame had been added to the document. It seems this has since been corrected in
29264          * Opera so the behaviour has been reverted, the URL will be set before being added.
29265          */
29266         Ext.fly(frame).set({
29267             id: id,
29268             name: id,
29269             cls: Ext.baseCSSPrefix + 'hide-display',
29270             src: Ext.SSL_SECURE_URL
29271         });
29272
29273         document.body.appendChild(frame);
29274
29275         // This is required so that IE doesn't pop the response up in a new window.
29276         if (document.frames) {
29277            document.frames[id].name = id;
29278         }
29279
29280         Ext.fly(form).set({
29281             target: id,
29282             method: 'POST',
29283             enctype: encoding,
29284             encoding: encoding,
29285             action: url || buf.action
29286         });
29287
29288         // add dynamic params
29289         if (params) {
29290             Ext.iterate(Ext.Object.fromQueryString(params), function(name, value){
29291                 hiddenItem = document.createElement('input');
29292                 Ext.fly(hiddenItem).set({
29293                     type: 'hidden',
29294                     value: value,
29295                     name: name
29296                 });
29297                 form.appendChild(hiddenItem);
29298                 hiddens.push(hiddenItem);
29299             });
29300         }
29301
29302         Ext.fly(frame).on('load', Ext.Function.bind(this.onUploadComplete, this, [frame, options]), null, {single: true});
29303         form.submit();
29304
29305         Ext.fly(form).set(buf);
29306         Ext.each(hiddens, function(h) {
29307             Ext.removeNode(h);
29308         });
29309     },
29310
29311     onUploadComplete: function(frame, options){
29312         var me = this,
29313             // bogus response object
29314             response = {
29315                 responseText: '',
29316                 responseXML: null
29317             }, doc, firstChild;
29318
29319         try {
29320             doc = frame.contentWindow.document || frame.contentDocument || window.frames[id].document;
29321             if (doc) {
29322                 if (doc.body) {
29323                     if (/textarea/i.test((firstChild = doc.body.firstChild || {}).tagName)) { // json response wrapped in textarea
29324                         response.responseText = firstChild.value;
29325                     } else {
29326                         response.responseText = doc.body.innerHTML;
29327                     }
29328                 }
29329                 //in IE the document may still have a body even if returns XML.
29330                 response.responseXML = doc.XMLDocument || doc;
29331             }
29332         } catch (e) {
29333         }
29334
29335         me.fireEvent('requestcomplete', me, response, options);
29336
29337         Ext.callback(options.success, options.scope, [response, options]);
29338         Ext.callback(options.callback, options.scope, [options, true, response]);
29339
29340         setTimeout(function(){
29341             Ext.removeNode(frame);
29342         }, 100);
29343     },
29344
29345     /**
29346      * Detect whether the form is intended to be used for an upload.
29347      * @private
29348      */
29349     isFormUpload: function(options){
29350         var form = this.getForm(options);
29351         if (form) {
29352             return (options.isUpload || (/multipart\/form-data/i).test(form.getAttribute('enctype')));
29353         }
29354         return false;
29355     },
29356
29357     /**
29358      * Get the form object from options.
29359      * @private
29360      * @param {Object} options The request options
29361      * @return {HTMLElement} The form, null if not passed
29362      */
29363     getForm: function(options){
29364         return Ext.getDom(options.form) || null;
29365     },
29366
29367     /**
29368      * Set various options such as the url, params for the request
29369      * @param {Object} options The initial options
29370      * @param {Object} scope The scope to execute in
29371      * @return {Object} The params for the request
29372      */
29373     setOptions: function(options, scope){
29374         var me =  this,
29375             params = options.params || {},
29376             extraParams = me.extraParams,
29377             urlParams = options.urlParams,
29378             url = options.url || me.url,
29379             jsonData = options.jsonData,
29380             method,
29381             disableCache,
29382             data;
29383
29384
29385         // allow params to be a method that returns the params object
29386         if (Ext.isFunction(params)) {
29387             params = params.call(scope, options);
29388         }
29389
29390         // allow url to be a method that returns the actual url
29391         if (Ext.isFunction(url)) {
29392             url = url.call(scope, options);
29393         }
29394
29395         url = this.setupUrl(options, url);
29396
29397         if (!url) {
29398             Ext.Error.raise({
29399                 options: options,
29400                 msg: 'No URL specified'
29401             });
29402         }
29403
29404         // check for xml or json data, and make sure json data is encoded
29405         data = options.rawData || options.xmlData || jsonData || null;
29406         if (jsonData && !Ext.isPrimitive(jsonData)) {
29407             data = Ext.encode(data);
29408         }
29409
29410         // make sure params are a url encoded string and include any extraParams if specified
29411         if (Ext.isObject(params)) {
29412             params = Ext.Object.toQueryString(params);
29413         }
29414
29415         if (Ext.isObject(extraParams)) {
29416             extraParams = Ext.Object.toQueryString(extraParams);
29417         }
29418
29419         params = params + ((extraParams) ? ((params) ? '&' : '') + extraParams : '');
29420
29421         urlParams = Ext.isObject(urlParams) ? Ext.Object.toQueryString(urlParams) : urlParams;
29422
29423         params = this.setupParams(options, params);
29424
29425         // decide the proper method for this request
29426         method = (options.method || me.method || ((params || data) ? 'POST' : 'GET')).toUpperCase();
29427         this.setupMethod(options, method);
29428
29429
29430         disableCache = options.disableCaching !== false ? (options.disableCaching || me.disableCaching) : false;
29431         // if the method is get append date to prevent caching
29432         if (method === 'GET' && disableCache) {
29433             url = Ext.urlAppend(url, (options.disableCachingParam || me.disableCachingParam) + '=' + (new Date().getTime()));
29434         }
29435
29436         // if the method is get or there is json/xml data append the params to the url
29437         if ((method == 'GET' || data) && params) {
29438             url = Ext.urlAppend(url, params);
29439             params = null;
29440         }
29441
29442         // allow params to be forced into the url
29443         if (urlParams) {
29444             url = Ext.urlAppend(url, urlParams);
29445         }
29446
29447         return {
29448             url: url,
29449             method: method,
29450             data: data || params || null
29451         };
29452     },
29453
29454     /**
29455      * Template method for overriding url
29456      * @private
29457      * @param {Object} options
29458      * @param {String} url
29459      * @return {String} The modified url
29460      */
29461     setupUrl: function(options, url){
29462         var form = this.getForm(options);
29463         if (form) {
29464             url = url || form.action;
29465         }
29466         return url;
29467     },
29468
29469
29470     /**
29471      * Template method for overriding params
29472      * @private
29473      * @param {Object} options
29474      * @param {String} params
29475      * @return {String} The modified params
29476      */
29477     setupParams: function(options, params) {
29478         var form = this.getForm(options),
29479             serializedForm;
29480         if (form && !this.isFormUpload(options)) {
29481             serializedForm = Ext.core.Element.serializeForm(form);
29482             params = params ? (params + '&' + serializedForm) : serializedForm;
29483         }
29484         return params;
29485     },
29486
29487     /**
29488      * Template method for overriding method
29489      * @private
29490      * @param {Object} options
29491      * @param {String} method
29492      * @return {String} The modified method
29493      */
29494     setupMethod: function(options, method){
29495         if (this.isFormUpload(options)) {
29496             return 'POST';
29497         }
29498         return method;
29499     },
29500
29501     /**
29502      * Setup all the headers for the request
29503      * @private
29504      * @param {Object} xhr The xhr object
29505      * @param {Object} options The options for the request
29506      * @param {Object} data The data for the request
29507      * @param {Object} params The params for the request
29508      */
29509     setupHeaders: function(xhr, options, data, params){
29510         var me = this,
29511             headers = Ext.apply({}, options.headers || {}, me.defaultHeaders || {}),
29512             contentType = me.defaultPostHeader,
29513             jsonData = options.jsonData,
29514             xmlData = options.xmlData,
29515             key,
29516             header;
29517
29518         if (!headers['Content-Type'] && (data || params)) {
29519             if (data) {
29520                 if (options.rawData) {
29521                     contentType = 'text/plain';
29522                 } else {
29523                     if (xmlData && Ext.isDefined(xmlData)) {
29524                         contentType = 'text/xml';
29525                     } else if (jsonData && Ext.isDefined(jsonData)) {
29526                         contentType = 'application/json';
29527                     }
29528                 }
29529             }
29530             headers['Content-Type'] = contentType;
29531         }
29532
29533         if (me.useDefaultXhrHeader && !headers['X-Requested-With']) {
29534             headers['X-Requested-With'] = me.defaultXhrHeader;
29535         }
29536         // set up all the request headers on the xhr object
29537         try{
29538             for (key in headers) {
29539                 if (headers.hasOwnProperty(key)) {
29540                     header = headers[key];
29541                     xhr.setRequestHeader(key, header);
29542                 }
29543
29544             }
29545         } catch(e) {
29546             me.fireEvent('exception', key, header);
29547         }
29548         return headers;
29549     },
29550
29551     /**
29552      * Creates the appropriate XHR transport for the browser.
29553      * @private
29554      */
29555     getXhrInstance: (function(){
29556         var options = [function(){
29557             return new XMLHttpRequest();
29558         }, function(){
29559             return new ActiveXObject('MSXML2.XMLHTTP.3.0');
29560         }, function(){
29561             return new ActiveXObject('MSXML2.XMLHTTP');
29562         }, function(){
29563             return new ActiveXObject('Microsoft.XMLHTTP');
29564         }], i = 0,
29565             len = options.length,
29566             xhr;
29567
29568         for(; i < len; ++i) {
29569             try{
29570                 xhr = options[i];
29571                 xhr();
29572                 break;
29573             }catch(e){}
29574         }
29575         return xhr;
29576     })(),
29577
29578     /**
29579      * Determine whether this object has a request outstanding.
29580      * @param {Object} request (Optional) defaults to the last transaction
29581      * @return {Boolean} True if there is an outstanding request.
29582      */
29583     isLoading : function(request) {
29584         if (!(request && request.xhr)) {
29585             return false;
29586         }
29587         // if there is a connection and readyState is not 0 or 4
29588         var state = request.xhr.readyState;
29589         return !(state === 0 || state == 4);
29590     },
29591
29592     /**
29593      * Aborts any outstanding request.
29594      * @param {Object} request (Optional) defaults to the last request
29595      */
29596     abort : function(request) {
29597         var me = this,
29598             requests = me.requests,
29599             id;
29600
29601         if (request && me.isLoading(request)) {
29602             /**
29603              * Clear out the onreadystatechange here, this allows us
29604              * greater control, the browser may/may not fire the function
29605              * depending on a series of conditions.
29606              */
29607             request.xhr.onreadystatechange = null;
29608             request.xhr.abort();
29609             me.clearTimeout(request);
29610             if (!request.timedout) {
29611                 request.aborted = true;
29612             }
29613             me.onComplete(request);
29614             me.cleanup(request);
29615         } else if (!request) {
29616             for(id in requests) {
29617                 if (requests.hasOwnProperty(id)) {
29618                     me.abort(requests[id]);
29619                 }
29620             }
29621         }
29622     },
29623
29624     /**
29625      * Fires when the state of the xhr changes
29626      * @private
29627      * @param {Object} request The request
29628      */
29629     onStateChange : function(request) {
29630         if (request.xhr.readyState == 4) {
29631             this.clearTimeout(request);
29632             this.onComplete(request);
29633             this.cleanup(request);
29634         }
29635     },
29636
29637     /**
29638      * Clear the timeout on the request
29639      * @private
29640      * @param {Object} The request
29641      */
29642     clearTimeout: function(request){
29643         clearTimeout(request.timeout);
29644         delete request.timeout;
29645     },
29646
29647     /**
29648      * Clean up any left over information from the request
29649      * @private
29650      * @param {Object} The request
29651      */
29652     cleanup: function(request){
29653         request.xhr = null;
29654         delete request.xhr;
29655     },
29656
29657     /**
29658      * To be called when the request has come back from the server
29659      * @private
29660      * @param {Object} request
29661      * @return {Object} The response
29662      */
29663     onComplete : function(request) {
29664         var me = this,
29665             options = request.options,
29666             result = me.parseStatus(request.xhr.status),
29667             success = result.success,
29668             response;
29669
29670         if (success) {
29671             response = me.createResponse(request);
29672             me.fireEvent('requestcomplete', me, response, options);
29673             Ext.callback(options.success, options.scope, [response, options]);
29674         } else {
29675             if (result.isException || request.aborted || request.timedout) {
29676                 response = me.createException(request);
29677             } else {
29678                 response = me.createResponse(request);
29679             }
29680             me.fireEvent('requestexception', me, response, options);
29681             Ext.callback(options.failure, options.scope, [response, options]);
29682         }
29683         Ext.callback(options.callback, options.scope, [options, success, response]);
29684         delete me.requests[request.id];
29685         return response;
29686     },
29687
29688     /**
29689      * Check if the response status was successful
29690      * @param {Number} status The status code
29691      * @return {Object} An object containing success/status state
29692      */
29693     parseStatus: function(status) {
29694         // see: https://prototype.lighthouseapp.com/projects/8886/tickets/129-ie-mangles-http-response-status-code-204-to-1223
29695         status = status == 1223 ? 204 : status;
29696
29697         var success = (status >= 200 && status < 300) || status == 304,
29698             isException = false;
29699
29700         if (!success) {
29701             switch (status) {
29702                 case 12002:
29703                 case 12029:
29704                 case 12030:
29705                 case 12031:
29706                 case 12152:
29707                 case 13030:
29708                     isException = true;
29709                     break;
29710             }
29711         }
29712         return {
29713             success: success,
29714             isException: isException
29715         };
29716     },
29717
29718     /**
29719      * Create the response object
29720      * @private
29721      * @param {Object} request
29722      */
29723     createResponse : function(request) {
29724         var xhr = request.xhr,
29725             headers = {},
29726             lines = xhr.getAllResponseHeaders().replace(/\r\n/g, '\n').split('\n'),
29727             count = lines.length,
29728             line, index, key, value, response;
29729
29730         while (count--) {
29731             line = lines[count];
29732             index = line.indexOf(':');
29733             if(index >= 0) {
29734                 key = line.substr(0, index).toLowerCase();
29735                 if (line.charAt(index + 1) == ' ') {
29736                     ++index;
29737                 }
29738                 headers[key] = line.substr(index + 1);
29739             }
29740         }
29741
29742         request.xhr = null;
29743         delete request.xhr;
29744
29745         response = {
29746             request: request,
29747             requestId : request.id,
29748             status : xhr.status,
29749             statusText : xhr.statusText,
29750             getResponseHeader : function(header){ return headers[header.toLowerCase()]; },
29751             getAllResponseHeaders : function(){ return headers; },
29752             responseText : xhr.responseText,
29753             responseXML : xhr.responseXML
29754         };
29755
29756         // If we don't explicitly tear down the xhr reference, IE6/IE7 will hold this in the closure of the
29757         // functions created with getResponseHeader/getAllResponseHeaders
29758         xhr = null;
29759         return response;
29760     },
29761
29762     /**
29763      * Create the exception object
29764      * @private
29765      * @param {Object} request
29766      */
29767     createException : function(request) {
29768         return {
29769             request : request,
29770             requestId : request.id,
29771             status : request.aborted ? -1 : 0,
29772             statusText : request.aborted ? 'transaction aborted' : 'communication failure',
29773             aborted: request.aborted,
29774             timedout: request.timedout
29775         };
29776     }
29777 });
29778
29779 /**
29780  * @class Ext.Ajax
29781  * @singleton
29782  * @markdown
29783  * @extends Ext.data.Connection
29784
29785 A singleton instance of an {@link Ext.data.Connection}. This class
29786 is used to communicate with your server side code. It can be used as follows:
29787
29788     Ext.Ajax.request({
29789         url: 'page.php',
29790         params: {
29791             id: 1
29792         },
29793         success: function(response){
29794             var text = response.responseText;
29795             // process server response here
29796         }
29797     });
29798
29799 Default options for all requests can be set be changing a property on the Ext.Ajax class:
29800
29801     Ext.Ajax.timeout = 60000; // 60 seconds
29802
29803 Any options specified in the request method for the Ajax request will override any
29804 defaults set on the Ext.Ajax class. In the code sample below, the timeout for the
29805 request will be 60 seconds.
29806
29807     Ext.Ajax.timeout = 120000; // 120 seconds
29808     Ext.Ajax.request({
29809         url: 'page.aspx',
29810         timeout: 60000
29811     });
29812
29813 In general, this class will be used for all Ajax requests in your application.
29814 The main reason for creating a separate {@link Ext.data.Connection} is for a
29815 series of requests that share common settings that are different to all other
29816 requests in the application.
29817
29818  */
29819 Ext.define('Ext.Ajax', {
29820     extend: 'Ext.data.Connection',
29821     singleton: true,
29822
29823     /**
29824      * @cfg {String} url @hide
29825      */
29826     /**
29827      * @cfg {Object} extraParams @hide
29828      */
29829     /**
29830      * @cfg {Object} defaultHeaders @hide
29831      */
29832     /**
29833      * @cfg {String} method (Optional) @hide
29834      */
29835     /**
29836      * @cfg {Number} timeout (Optional) @hide
29837      */
29838     /**
29839      * @cfg {Boolean} autoAbort (Optional) @hide
29840      */
29841
29842     /**
29843      * @cfg {Boolean} disableCaching (Optional) @hide
29844      */
29845
29846     /**
29847      * @property  disableCaching
29848      * True to add a unique cache-buster param to GET requests. (defaults to true)
29849      * @type Boolean
29850      */
29851     /**
29852      * @property  url
29853      * The default URL to be used for requests to the server. (defaults to undefined)
29854      * If the server receives all requests through one URL, setting this once is easier than
29855      * entering it on every request.
29856      * @type String
29857      */
29858     /**
29859      * @property  extraParams
29860      * An object containing properties which are used as extra parameters to each request made
29861      * by this object (defaults to undefined). Session information and other data that you need
29862      * to pass with each request are commonly put here.
29863      * @type Object
29864      */
29865     /**
29866      * @property  defaultHeaders
29867      * An object containing request headers which are added to each request made by this object
29868      * (defaults to undefined).
29869      * @type Object
29870      */
29871     /**
29872      * @property  method
29873      * The default HTTP method to be used for requests. Note that this is case-sensitive and
29874      * should be all caps (defaults to undefined; if not set but params are present will use
29875      * <tt>"POST"</tt>, otherwise will use <tt>"GET"</tt>.)
29876      * @type String
29877      */
29878     /**
29879      * @property  timeout
29880      * The timeout in milliseconds to be used for requests. (defaults to 30000)
29881      * @type Number
29882      */
29883
29884     /**
29885      * @property  autoAbort
29886      * Whether a new request should abort any pending requests. (defaults to false)
29887      * @type Boolean
29888      */
29889     autoAbort : false
29890 });
29891 /**
29892  * @author Ed Spencer
29893  * @class Ext.data.Association
29894  * @extends Object
29895  *
29896  * <p>Associations enable you to express relationships between different {@link Ext.data.Model Models}. Let's say we're
29897  * writing an ecommerce system where Users can make Orders - there's a relationship between these Models that we can
29898  * express like this:</p>
29899  *
29900 <pre><code>
29901 Ext.define('User', {
29902     extend: 'Ext.data.Model',
29903     fields: ['id', 'name', 'email'],
29904
29905     hasMany: {model: 'Order', name: 'orders'}
29906 });
29907
29908 Ext.define('Order', {
29909     extend: 'Ext.data.Model',
29910     fields: ['id', 'user_id', 'status', 'price'],
29911
29912     belongsTo: 'User'
29913 });
29914 </code></pre>
29915  *
29916  * <p>We've set up two models - User and Order - and told them about each other. You can set up as many associations on
29917  * each Model as you need using the two default types - {@link Ext.data.HasManyAssociation hasMany} and
29918  * {@link Ext.data.BelongsToAssociation belongsTo}. There's much more detail on the usage of each of those inside their
29919  * documentation pages. If you're not familiar with Models already, {@link Ext.data.Model there is plenty on those too}.</p>
29920  *
29921  * <p><u>Further Reading</u></p>
29922  *
29923  * <ul style="list-style-type: disc; padding-left: 20px;">
29924  *   <li>{@link Ext.data.HasManyAssociation hasMany associations}
29925  *   <li>{@link Ext.data.BelongsToAssociation belongsTo associations}
29926  *   <li>{@link Ext.data.Model using Models}
29927  * </ul>
29928  * 
29929  * <b>Self association models</b>
29930  * <p>We can also have models that create parent/child associations between the same type. Below is an example, where
29931  * groups can be nested inside other groups:</p>
29932  * <pre><code>
29933
29934 // Server Data
29935 {
29936     "groups": {
29937         "id": 10,
29938         "parent_id": 100,
29939         "name": "Main Group",
29940         "parent_group": {
29941             "id": 100,
29942             "parent_id": null,
29943             "name": "Parent Group"
29944         },
29945         "child_groups": [{
29946             "id": 2,
29947             "parent_id": 10,
29948             "name": "Child Group 1"
29949         },{
29950             "id": 3,
29951             "parent_id": 10,
29952             "name": "Child Group 2"
29953         },{
29954             "id": 4,
29955             "parent_id": 10,
29956             "name": "Child Group 3"
29957         }]
29958     }
29959 }
29960
29961 // Client code
29962 Ext.define('Group', {
29963     extend: 'Ext.data.Model',
29964     fields: ['id', 'parent_id', 'name'],
29965     proxy: {
29966         type: 'ajax',
29967         url: 'data.json',
29968         reader: {
29969             type: 'json',
29970             root: 'groups'
29971         }
29972     },
29973     associations: [{
29974         type: 'hasMany',
29975         model: 'Group',
29976         primaryKey: 'id',
29977         foreignKey: 'parent_id',
29978         autoLoad: true,
29979         associationKey: 'child_groups' // read child data from child_groups
29980     }, {
29981         type: 'belongsTo',
29982         model: 'Group',
29983         primaryKey: 'id',
29984         foreignKey: 'parent_id',
29985         autoLoad: true,
29986         associationKey: 'parent_group' // read parent data from parent_group
29987     }]
29988 });
29989
29990
29991 Ext.onReady(function(){
29992     
29993     Group.load(10, {
29994         success: function(group){
29995             console.log(group.getGroup().get('name'));
29996             
29997             group.groups().each(function(rec){
29998                 console.log(rec.get('name'));
29999             });
30000         }
30001     });
30002     
30003 });
30004  * </code></pre>
30005  *
30006  * @constructor
30007  * @param {Object} config Optional config object
30008  */
30009 Ext.define('Ext.data.Association', {
30010     /**
30011      * @cfg {String} ownerModel The string name of the model that owns the association. Required
30012      */
30013
30014     /**
30015      * @cfg {String} associatedModel The string name of the model that is being associated with. Required
30016      */
30017
30018     /**
30019      * @cfg {String} primaryKey The name of the primary key on the associated model. Defaults to 'id'.
30020      * In general this will be the {@link Ext.data.Model#idProperty} of the Model.
30021      */
30022     primaryKey: 'id',
30023
30024     /**
30025      * @cfg {Ext.data.reader.Reader} reader A special reader to read associated data
30026      */
30027     
30028     /**
30029      * @cfg {String} associationKey The name of the property in the data to read the association from.
30030      * Defaults to the name of the associated model.
30031      */
30032
30033     defaultReaderType: 'json',
30034
30035     statics: {
30036         create: function(association){
30037             if (!association.isAssociation) {
30038                 if (Ext.isString(association)) {
30039                     association = {
30040                         type: association
30041                     };
30042                 }
30043
30044                 switch (association.type) {
30045                     case 'belongsTo':
30046                         return Ext.create('Ext.data.BelongsToAssociation', association);
30047                     case 'hasMany':
30048                         return Ext.create('Ext.data.HasManyAssociation', association);
30049                     //TODO Add this back when it's fixed
30050 //                    case 'polymorphic':
30051 //                        return Ext.create('Ext.data.PolymorphicAssociation', association);
30052                     default:
30053                         Ext.Error.raise('Unknown Association type: "' + association.type + '"');
30054                 }
30055             }
30056             return association;
30057         }
30058     },
30059
30060     constructor: function(config) {
30061         Ext.apply(this, config);
30062
30063         var types           = Ext.ModelManager.types,
30064             ownerName       = config.ownerModel,
30065             associatedName  = config.associatedModel,
30066             ownerModel      = types[ownerName],
30067             associatedModel = types[associatedName],
30068             ownerProto;
30069
30070         if (ownerModel === undefined) {
30071             Ext.Error.raise("The configured ownerModel was not valid (you tried " + ownerName + ")");
30072         }
30073         if (associatedModel === undefined) {
30074             Ext.Error.raise("The configured associatedModel was not valid (you tried " + associatedName + ")");
30075         }
30076
30077         this.ownerModel = ownerModel;
30078         this.associatedModel = associatedModel;
30079
30080         /**
30081          * The name of the model that 'owns' the association
30082          * @property ownerName
30083          * @type String
30084          */
30085
30086         /**
30087          * The name of the model is on the other end of the association (e.g. if a User model hasMany Orders, this is 'Order')
30088          * @property associatedName
30089          * @type String
30090          */
30091
30092         Ext.applyIf(this, {
30093             ownerName : ownerName,
30094             associatedName: associatedName
30095         });
30096     },
30097
30098     /**
30099      * Get a specialized reader for reading associated data
30100      * @return {Ext.data.reader.Reader} The reader, null if not supplied
30101      */
30102     getReader: function(){
30103         var me = this,
30104             reader = me.reader,
30105             model = me.associatedModel;
30106
30107         if (reader) {
30108             if (Ext.isString(reader)) {
30109                 reader = {
30110                     type: reader
30111                 };
30112             }
30113             if (reader.isReader) {
30114                 reader.setModel(model);
30115             } else {
30116                 Ext.applyIf(reader, {
30117                     model: model,
30118                     type : me.defaultReaderType
30119                 });
30120             }
30121             me.reader = Ext.createByAlias('reader.' + reader.type, reader);
30122         }
30123         return me.reader || null;
30124     }
30125 });
30126
30127 /**
30128  * @author Ed Spencer
30129  * @class Ext.ModelManager
30130  * @extends Ext.AbstractManager
30131
30132 The ModelManager keeps track of all {@link Ext.data.Model} types defined in your application.
30133
30134 __Creating Model Instances__
30135 Model instances can be created by using the {@link #create} function. It is also possible to do
30136 this by using the Model type directly. The following snippets are equivalent:
30137
30138     Ext.define('User', {
30139         extend: 'Ext.data.Model',
30140         fields: ['first', 'last']
30141     });
30142     
30143     // method 1, create through the manager
30144     Ext.ModelManager.create({
30145         first: 'Ed',
30146         last: 'Spencer'
30147     }, 'User');
30148     
30149     // method 2, create on the type directly
30150     new User({
30151         first: 'Ed',
30152         last: 'Spencer'
30153     });
30154     
30155 __Accessing Model Types__
30156 A reference to a Model type can be obtained by using the {@link #getModel} function. Since models types
30157 are normal classes, you can access the type directly. The following snippets are equivalent:
30158
30159     Ext.define('User', {
30160         extend: 'Ext.data.Model',
30161         fields: ['first', 'last']
30162     });
30163     
30164     // method 1, access model type through the manager
30165     var UserType = Ext.ModelManager.getModel('User');
30166     
30167     // method 2, reference the type directly
30168     var UserType = User;
30169
30170  * @markdown
30171  * @singleton
30172  */
30173 Ext.define('Ext.ModelManager', {
30174     extend: 'Ext.AbstractManager',
30175     alternateClassName: 'Ext.ModelMgr',
30176     requires: ['Ext.data.Association'],
30177     
30178     singleton: true,
30179     
30180     typeName: 'mtype',
30181     
30182     /**
30183      * Private stack of associations that must be created once their associated model has been defined
30184      * @property associationStack
30185      * @type Array
30186      */
30187     associationStack: [],
30188     
30189     /**
30190      * Registers a model definition. All model plugins marked with isDefault: true are bootstrapped
30191      * immediately, as are any addition plugins defined in the model config.
30192      * @private
30193      */
30194     registerType: function(name, config) {
30195         var proto = config.prototype,
30196             model;
30197         if (proto && proto.isModel) {
30198             // registering an already defined model
30199             model = config;
30200         } else {
30201             // passing in a configuration
30202             if (!config.extend) {
30203                 config.extend = 'Ext.data.Model';
30204             }
30205             model = Ext.define(name, config);
30206         }
30207         this.types[name] = model;
30208         return model;
30209     },
30210     
30211     /**
30212      * @private
30213      * Private callback called whenever a model has just been defined. This sets up any associations
30214      * that were waiting for the given model to be defined
30215      * @param {Function} model The model that was just created
30216      */
30217     onModelDefined: function(model) {
30218         var stack  = this.associationStack,
30219             length = stack.length,
30220             create = [],
30221             association, i, created;
30222         
30223         for (i = 0; i < length; i++) {
30224             association = stack[i];
30225             
30226             if (association.associatedModel == model.modelName) {
30227                 create.push(association);
30228             }
30229         }
30230         
30231         for (i = 0, length = create.length; i < length; i++) {
30232             created = create[i];
30233             this.types[created.ownerModel].prototype.associations.add(Ext.data.Association.create(created));
30234             Ext.Array.remove(stack, created);
30235         }
30236     },
30237     
30238     /**
30239      * Registers an association where one of the models defined doesn't exist yet.
30240      * The ModelManager will check when new models are registered if it can link them
30241      * together
30242      * @private
30243      * @param {Ext.data.Association} association The association
30244      */
30245     registerDeferredAssociation: function(association){
30246         this.associationStack.push(association);
30247     },
30248     
30249     /**
30250      * Returns the {@link Ext.data.Model} for a given model name
30251      * @param {String/Object} id The id of the model or the model instance.
30252      */
30253     getModel: function(id) {
30254         var model = id;
30255         if (typeof model == 'string') {
30256             model = this.types[model];
30257         }
30258         return model;
30259     },
30260     
30261     /**
30262      * Creates a new instance of a Model using the given data.
30263      * @param {Object} data Data to initialize the Model's fields with
30264      * @param {String} name The name of the model to create
30265      * @param {Number} id Optional unique id of the Model instance (see {@link Ext.data.Model})
30266      */
30267     create: function(config, name, id) {
30268         var con = typeof name == 'function' ? name : this.types[name || config.name];
30269         
30270         return new con(config, id);
30271     }
30272 }, function() {
30273     
30274     /**
30275      * Creates a new Model class from the specified config object. See {@link Ext.data.Model} for full examples.
30276      * 
30277      * @param {Object} config A configuration object for the Model you wish to create.
30278      * @return {Ext.data.Model} The newly registered Model
30279      * @member Ext
30280      * @method regModel
30281      */
30282     Ext.regModel = function() {
30283         if (Ext.isDefined(Ext.global.console)) {
30284             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: []});.');
30285         }
30286         return this.ModelManager.registerType.apply(this.ModelManager, arguments);
30287     };
30288 });
30289
30290 /**
30291  * @class Ext.app.Controller
30292  * @constructor
30293  * 
30294  * Controllers are the glue that binds an application together. All they really do is listen for events (usually from
30295  * views) and take some action. Here's how we might create a Controller to manage Users:
30296  * 
30297  *     Ext.define('MyApp.controller.Users', {
30298  *         extend: 'Ext.app.Controller',
30299  * 
30300  *         init: function() {
30301  *             console.log('Initialized Users! This happens before the Application launch function is called');
30302  *         }
30303  *     });
30304  * 
30305  * The init function is a special method that is called when your application boots. It is called before the 
30306  * {@link Ext.app.Application Application}'s launch function is executed so gives a hook point to run any code before
30307  * your Viewport is created.
30308  * 
30309  * The init function is a great place to set up how your controller interacts with the view, and is usually used in 
30310  * conjunction with another Controller function - {@link Ext.app.Controller#control control}. The control function 
30311  * makes it easy to listen to events on your view classes and take some action with a handler function. Let's update
30312  * our Users controller to tell us when the panel is rendered:
30313  * 
30314  *     Ext.define('MyApp.controller.Users', {
30315  *         extend: 'Ext.app.Controller',
30316  * 
30317  *         init: function() {
30318  *             this.control({
30319  *                 'viewport > panel': {
30320  *                     render: this.onPanelRendered
30321  *                 }
30322  *             });
30323  *         },
30324  * 
30325  *         onPanelRendered: function() {
30326  *             console.log('The panel was rendered');
30327  *         }
30328  *     });
30329  * 
30330  * We've updated the init function to use this.control to set up listeners on views in our application. The control
30331  * function uses the new ComponentQuery engine to quickly and easily get references to components on the page. If you
30332  * are not familiar with ComponentQuery yet, be sure to check out THIS GUIDE for a full explanation. In brief though,
30333  * it allows us to pass a CSS-like selector that will find every matching component on the page.
30334  * 
30335  * In our init function above we supplied 'viewport > panel', which translates to "find me every Panel that is a direct
30336  * child of a Viewport". We then supplied an object that maps event names (just 'render' in this case) to handler 
30337  * functions. The overall effect is that whenever any component that matches our selector fires a 'render' event, our 
30338  * onPanelRendered function is called.
30339  * 
30340  * <u>Using refs</u>
30341  * 
30342  * One of the most useful parts of Controllers is the new ref system. These use the new {@link Ext.ComponentQuery} to
30343  * make it really easy to get references to Views on your page. Let's look at an example of this now:
30344  * 
30345  * Ext.define('MyApp.controller.Users', {
30346      extend: 'Ext.app.Controller',
30347
30348      refs: [
30349          {
30350              ref: 'list',
30351              selector: 'grid'
30352          }
30353      ],
30354
30355      init: function() {
30356          this.control({
30357              'button': {
30358                  click: this.refreshGrid
30359              }
30360          });
30361      },
30362
30363      refreshGrid: function() {
30364          this.getList().store.load();
30365      }
30366  });
30367  * 
30368  * This example assumes the existence of a {@link Ext.grid.Panel Grid} on the page, which contains a single button to 
30369  * refresh the Grid when clicked. In our refs array, we set up a reference to the grid. There are two parts to this - 
30370  * the 'selector', which is a {@link Ext.ComponentQuery ComponentQuery} selector which finds any grid on the page and
30371  * assigns it to the reference 'list'.
30372  * 
30373  * By giving the reference a name, we get a number of things for free. The first is the getList function that we use in
30374  * the refreshGrid method above. This is generated automatically by the Controller based on the name of our ref, which 
30375  * was capitalized and prepended with get to go from 'list' to 'getList'.
30376  * 
30377  * The way this works is that the first time getList is called by your code, the ComponentQuery selector is run and the
30378  * first component that matches the selector ('grid' in this case) will be returned. All future calls to getList will 
30379  * use a cached reference to that grid. Usually it is advised to use a specific ComponentQuery selector that will only
30380  * match a single View in your application (in the case above our selector will match any grid on the page).
30381  * 
30382  * Bringing it all together, our init function is called when the application boots, at which time we call this.control
30383  * to listen to any click on a {@link Ext.button.Button button} and call our refreshGrid function (again, this will 
30384  * match any button on the page so we advise a more specific selector than just 'button', but have left it this way for
30385  * simplicity). When the button is clicked we use out getList function to refresh the grid.
30386  * 
30387  * You can create any number of refs and control any number of components this way, simply adding more functions to 
30388  * your Controller as you go. For an example of real-world usage of Controllers see the Feed Viewer example in the 
30389  * examples/app/feed-viewer folder in the SDK download.
30390  * 
30391  * <u>Generated getter methods</u>
30392  * 
30393  * Refs aren't the only thing that generate convenient getter methods. Controllers often have to deal with Models and 
30394  * Stores so the framework offers a couple of easy ways to get access to those too. Let's look at another example:
30395  * 
30396  * Ext.define('MyApp.controller.Users', {
30397      extend: 'Ext.app.Controller',
30398
30399      models: ['User'],
30400      stores: ['AllUsers', 'AdminUsers'],
30401
30402      init: function() {
30403          var User = this.getUserModel(),
30404              allUsers = this.getAllUsersStore();
30405
30406          var ed = new User({name: 'Ed'});
30407          allUsers.add(ed);
30408      }
30409  });
30410  * 
30411  * By specifying Models and Stores that the Controller cares about, it again dynamically loads them from the appropriate
30412  * locations (app/model/User.js, app/store/AllUsers.js and app/store/AdminUsers.js in this case) and creates getter 
30413  * functions for them all. The example above will create a new User model instance and add it to the AllUsers Store.
30414  * Of course, you could do anything in this function but in this case we just did something simple to demonstrate the 
30415  * functionality.
30416  * 
30417  * <u>Further Reading</u>
30418  * 
30419  * For more information about writing Ext JS 4 applications, please see the <a href="../guide/application_architecture">
30420  * application architecture guide</a>. Also see the {@link Ext.app.Application} documentation.
30421  * 
30422  * @markdown
30423  * @docauthor Ed Spencer
30424  */  
30425 Ext.define('Ext.app.Controller', {
30426     /**
30427      * @cfg {Object} id The id of this controller. You can use this id when dispatching.
30428      */
30429
30430     mixins: {
30431         observable: 'Ext.util.Observable'
30432     },
30433
30434     onClassExtended: function(cls, data) {
30435         var className = Ext.getClassName(cls),
30436             match = className.match(/^(.*)\.controller\./);
30437
30438         if (match !== null) {
30439             var namespace = Ext.Loader.getPrefix(className) || match[1],
30440                 onBeforeClassCreated = data.onBeforeClassCreated,
30441                 requires = [],
30442                 modules = ['model', 'view', 'store'],
30443                 prefix;
30444
30445             data.onBeforeClassCreated = function(cls, data) {
30446                 var i, ln, module,
30447                     items, j, subLn, item;
30448
30449                 for (i = 0,ln = modules.length; i < ln; i++) {
30450                     module = modules[i];
30451
30452                     items = Ext.Array.from(data[module + 's']);
30453
30454                     for (j = 0,subLn = items.length; j < subLn; j++) {
30455                         item = items[j];
30456
30457                         prefix = Ext.Loader.getPrefix(item);
30458
30459                         if (prefix === '' || prefix === item) {
30460                             requires.push(namespace + '.' + module + '.' + item);
30461                         }
30462                         else {
30463                             requires.push(item);
30464                         }
30465                     }
30466                 }
30467
30468                 Ext.require(requires, Ext.Function.pass(onBeforeClassCreated, arguments, this));
30469             };
30470         }
30471     },
30472
30473     constructor: function(config) {
30474         this.mixins.observable.constructor.call(this, config);
30475
30476         Ext.apply(this, config || {});
30477
30478         this.createGetters('model', this.models);
30479         this.createGetters('store', this.stores);
30480         this.createGetters('view', this.views);
30481
30482         if (this.refs) {
30483             this.ref(this.refs);
30484         }
30485     },
30486
30487     // Template method
30488     init: function(application) {},
30489     // Template method
30490     onLaunch: function(application) {},
30491
30492     createGetters: function(type, refs) {
30493         type = Ext.String.capitalize(type);
30494         Ext.Array.each(refs, function(ref) {
30495             var fn = 'get',
30496                 parts = ref.split('.');
30497
30498             // Handle namespaced class names. E.g. feed.Add becomes getFeedAddView etc.
30499             Ext.Array.each(parts, function(part) {
30500                 fn += Ext.String.capitalize(part);
30501             });
30502             fn += type;
30503
30504             if (!this[fn]) {
30505                 this[fn] = Ext.Function.pass(this['get' + type], [ref], this);
30506             }
30507             // Execute it right away
30508             this[fn](ref);
30509         },
30510         this);
30511     },
30512
30513     ref: function(refs) {
30514         var me = this;
30515         refs = Ext.Array.from(refs);
30516         Ext.Array.each(refs, function(info) {
30517             var ref = info.ref,
30518                 fn = 'get' + Ext.String.capitalize(ref);
30519             if (!me[fn]) {
30520                 me[fn] = Ext.Function.pass(me.getRef, [ref, info], me);
30521             }
30522         });
30523     },
30524
30525     getRef: function(ref, info, config) {
30526         this.refCache = this.refCache || {};
30527         info = info || {};
30528         config = config || {};
30529
30530         Ext.apply(info, config);
30531
30532         if (info.forceCreate) {
30533             return Ext.ComponentManager.create(info, 'component');
30534         }
30535
30536         var me = this,
30537             selector = info.selector,
30538             cached = me.refCache[ref];
30539
30540         if (!cached) {
30541             me.refCache[ref] = cached = Ext.ComponentQuery.query(info.selector)[0];
30542             if (!cached && info.autoCreate) {
30543                 me.refCache[ref] = cached = Ext.ComponentManager.create(info, 'component');
30544             }
30545             if (cached) {
30546                 cached.on('beforedestroy', function() {
30547                     me.refCache[ref] = null;
30548                 });
30549             }
30550         }
30551
30552         return cached;
30553     },
30554
30555     control: function(selectors, listeners) {
30556         this.application.control(selectors, listeners, this);
30557     },
30558
30559     getController: function(name) {
30560         return this.application.getController(name);
30561     },
30562
30563     getStore: function(name) {
30564         return this.application.getStore(name);
30565     },
30566
30567     getModel: function(model) {
30568         return this.application.getModel(model);
30569     },
30570
30571     getView: function(view) {
30572         return this.application.getView(view);
30573     }
30574 });
30575
30576 /**
30577  * @class Ext.data.SortTypes
30578  * This class defines a series of static methods that are used on a
30579  * {@link Ext.data.Field} for performing sorting. The methods cast the 
30580  * underlying values into a data type that is appropriate for sorting on
30581  * that particular field.  If a {@link Ext.data.Field#type} is specified, 
30582  * the sortType will be set to a sane default if the sortType is not 
30583  * explicitly defined on the field. The sortType will make any necessary
30584  * modifications to the value and return it.
30585  * <ul>
30586  * <li><b>asText</b> - Removes any tags and converts the value to a string</li>
30587  * <li><b>asUCText</b> - Removes any tags and converts the value to an uppercase string</li>
30588  * <li><b>asUCText</b> - Converts the value to an uppercase string</li>
30589  * <li><b>asDate</b> - Converts the value into Unix epoch time</li>
30590  * <li><b>asFloat</b> - Converts the value to a floating point number</li>
30591  * <li><b>asInt</b> - Converts the value to an integer number</li>
30592  * </ul>
30593  * <p>
30594  * It is also possible to create a custom sortType that can be used throughout
30595  * an application.
30596  * <pre><code>
30597 Ext.apply(Ext.data.SortTypes, {
30598     asPerson: function(person){
30599         // expects an object with a first and last name property
30600         return person.lastName.toUpperCase() + person.firstName.toLowerCase();
30601     }    
30602 });
30603
30604 Ext.define('Employee', {
30605     extend: 'Ext.data.Model',
30606     fields: [{
30607         name: 'person',
30608         sortType: 'asPerson'
30609     }, {
30610         name: 'salary',
30611         type: 'float' // sortType set to asFloat
30612     }]
30613 });
30614  * </code></pre>
30615  * </p>
30616  * @singleton
30617  * @docauthor Evan Trimboli <evan@sencha.com>
30618  */
30619 Ext.define('Ext.data.SortTypes', {
30620     
30621     singleton: true,
30622     
30623     /**
30624      * Default sort that does nothing
30625      * @param {Mixed} s The value being converted
30626      * @return {Mixed} The comparison value
30627      */
30628     none : function(s) {
30629         return s;
30630     },
30631
30632     /**
30633      * The regular expression used to strip tags
30634      * @type {RegExp}
30635      * @property
30636      */
30637     stripTagsRE : /<\/?[^>]+>/gi,
30638
30639     /**
30640      * Strips all HTML tags to sort on text only
30641      * @param {Mixed} s The value being converted
30642      * @return {String} The comparison value
30643      */
30644     asText : function(s) {
30645         return String(s).replace(this.stripTagsRE, "");
30646     },
30647
30648     /**
30649      * Strips all HTML tags to sort on text only - Case insensitive
30650      * @param {Mixed} s The value being converted
30651      * @return {String} The comparison value
30652      */
30653     asUCText : function(s) {
30654         return String(s).toUpperCase().replace(this.stripTagsRE, "");
30655     },
30656
30657     /**
30658      * Case insensitive string
30659      * @param {Mixed} s The value being converted
30660      * @return {String} The comparison value
30661      */
30662     asUCString : function(s) {
30663         return String(s).toUpperCase();
30664     },
30665
30666     /**
30667      * Date sorting
30668      * @param {Mixed} s The value being converted
30669      * @return {Number} The comparison value
30670      */
30671     asDate : function(s) {
30672         if(!s){
30673             return 0;
30674         }
30675         if(Ext.isDate(s)){
30676             return s.getTime();
30677         }
30678         return Date.parse(String(s));
30679     },
30680
30681     /**
30682      * Float sorting
30683      * @param {Mixed} s The value being converted
30684      * @return {Float} The comparison value
30685      */
30686     asFloat : function(s) {
30687         var val = parseFloat(String(s).replace(/,/g, ""));
30688         return isNaN(val) ? 0 : val;
30689     },
30690
30691     /**
30692      * Integer sorting
30693      * @param {Mixed} s The value being converted
30694      * @return {Number} The comparison value
30695      */
30696     asInt : function(s) {
30697         var val = parseInt(String(s).replace(/,/g, ""), 10);
30698         return isNaN(val) ? 0 : val;
30699     }
30700 });
30701 /**
30702  * @author Ed Spencer
30703  * @class Ext.data.Errors
30704  * @extends Ext.util.MixedCollection
30705  * 
30706  * <p>Wraps a collection of validation error responses and provides convenient functions for
30707  * accessing and errors for specific fields.</p>
30708  * 
30709  * <p>Usually this class does not need to be instantiated directly - instances are instead created
30710  * automatically when {@link Ext.data.Model#validate validate} on a model instance:</p>
30711  * 
30712 <pre><code>
30713 //validate some existing model instance - in this case it returned 2 failures messages
30714 var errors = myModel.validate();
30715
30716 errors.isValid(); //false
30717
30718 errors.length; //2
30719 errors.getByField('name');  // [{field: 'name',  error: 'must be present'}]
30720 errors.getByField('title'); // [{field: 'title', error: 'is too short'}]
30721 </code></pre>
30722  */
30723 Ext.define('Ext.data.Errors', {
30724     extend: 'Ext.util.MixedCollection',
30725     
30726     /**
30727      * Returns true if there are no errors in the collection
30728      * @return {Boolean} 
30729      */
30730     isValid: function() {
30731         return this.length === 0;
30732     },
30733     
30734     /**
30735      * Returns all of the errors for the given field
30736      * @param {String} fieldName The field to get errors for
30737      * @return {Array} All errors for the given field
30738      */
30739     getByField: function(fieldName) {
30740         var errors = [],
30741             error, field, i;
30742             
30743         for (i = 0; i < this.length; i++) {
30744             error = this.items[i];
30745             
30746             if (error.field == fieldName) {
30747                 errors.push(error);
30748             }
30749         }
30750         
30751         return errors;
30752     }
30753 });
30754
30755 /**
30756  * @author Ed Spencer
30757  * @class Ext.data.Operation
30758  * @extends Object
30759  * 
30760  * <p>Represents a single read or write operation performed by a {@link Ext.data.proxy.Proxy Proxy}.
30761  * Operation objects are used to enable communication between Stores and Proxies. Application
30762  * developers should rarely need to interact with Operation objects directly.</p>
30763  * 
30764  * <p>Several Operations can be batched together in a {@link Ext.data.Batch batch}.</p>
30765  * 
30766  * @constructor
30767  * @param {Object} config Optional config object
30768  */
30769 Ext.define('Ext.data.Operation', {
30770     /**
30771      * @cfg {Boolean} synchronous True if this Operation is to be executed synchronously (defaults to true). This
30772      * property is inspected by a {@link Ext.data.Batch Batch} to see if a series of Operations can be executed in
30773      * parallel or not.
30774      */
30775     synchronous: true,
30776     
30777     /**
30778      * @cfg {String} action The action being performed by this Operation. Should be one of 'create', 'read', 'update' or 'destroy'
30779      */
30780     action: undefined,
30781     
30782     /**
30783      * @cfg {Array} filters Optional array of filter objects. Only applies to 'read' actions.
30784      */
30785     filters: undefined,
30786     
30787     /**
30788      * @cfg {Array} sorters Optional array of sorter objects. Only applies to 'read' actions.
30789      */
30790     sorters: undefined,
30791     
30792     /**
30793      * @cfg {Object} group Optional grouping configuration. Only applies to 'read' actions where grouping is desired.
30794      */
30795     group: undefined,
30796     
30797     /**
30798      * @cfg {Number} start The start index (offset), used in paging when running a 'read' action.
30799      */
30800     start: undefined,
30801     
30802     /**
30803      * @cfg {Number} limit The number of records to load. Used on 'read' actions when paging is being used.
30804      */
30805     limit: undefined,
30806     
30807     /**
30808      * @cfg {Ext.data.Batch} batch The batch that this Operation is a part of (optional)
30809      */
30810     batch: undefined,
30811         
30812     /**
30813      * Read-only property tracking the start status of this Operation. Use {@link #isStarted}.
30814      * @property started
30815      * @type Boolean
30816      * @private
30817      */
30818     started: false,
30819     
30820     /**
30821      * Read-only property tracking the run status of this Operation. Use {@link #isRunning}.
30822      * @property running
30823      * @type Boolean
30824      * @private
30825      */
30826     running: false,
30827     
30828     /**
30829      * Read-only property tracking the completion status of this Operation. Use {@link #isComplete}.
30830      * @property complete
30831      * @type Boolean
30832      * @private
30833      */
30834     complete: false,
30835     
30836     /**
30837      * Read-only property tracking whether the Operation was successful or not. This starts as undefined and is set to true
30838      * or false by the Proxy that is executing the Operation. It is also set to false by {@link #setException}. Use
30839      * {@link #wasSuccessful} to query success status.
30840      * @property success
30841      * @type Boolean
30842      * @private
30843      */
30844     success: undefined,
30845     
30846     /**
30847      * Read-only property tracking the exception status of this Operation. Use {@link #hasException} and see {@link #getError}.
30848      * @property exception
30849      * @type Boolean
30850      * @private
30851      */
30852     exception: false,
30853     
30854     /**
30855      * The error object passed when {@link #setException} was called. This could be any object or primitive.
30856      * @property error
30857      * @type Mixed
30858      * @private
30859      */
30860     error: undefined,
30861     
30862     constructor: function(config) {
30863         Ext.apply(this, config || {});
30864     },
30865     
30866     /**
30867      * Marks the Operation as started
30868      */
30869     setStarted: function() {
30870         this.started = true;
30871         this.running = true;
30872     },
30873     
30874     /**
30875      * Marks the Operation as completed
30876      */
30877     setCompleted: function() {
30878         this.complete = true;
30879         this.running  = false;
30880     },
30881     
30882     /**
30883      * Marks the Operation as successful
30884      */
30885     setSuccessful: function() {
30886         this.success = true;
30887     },
30888     
30889     /**
30890      * Marks the Operation as having experienced an exception. Can be supplied with an option error message/object.
30891      * @param {Mixed} error Optional error string/object
30892      */
30893     setException: function(error) {
30894         this.exception = true;
30895         this.success = false;
30896         this.running = false;
30897         this.error = error;
30898     },
30899     
30900     /**
30901      * Returns true if this Operation encountered an exception (see also {@link #getError})
30902      * @return {Boolean} True if there was an exception
30903      */
30904     hasException: function() {
30905         return this.exception === true;
30906     },
30907     
30908     /**
30909      * Returns the error string or object that was set using {@link #setException}
30910      * @return {Mixed} The error object
30911      */
30912     getError: function() {
30913         return this.error;
30914     },
30915     
30916     /**
30917      * Returns an array of Ext.data.Model instances as set by the Proxy.
30918      * @return {Array} Any loaded Records
30919      */
30920     getRecords: function() {
30921         var resultSet = this.getResultSet();
30922         
30923         return (resultSet === undefined ? this.records : resultSet.records);
30924     },
30925     
30926     /**
30927      * Returns the ResultSet object (if set by the Proxy). This object will contain the {@link Ext.data.Model model} instances
30928      * as well as meta data such as number of instances fetched, number available etc
30929      * @return {Ext.data.ResultSet} The ResultSet object
30930      */
30931     getResultSet: function() {
30932         return this.resultSet;
30933     },
30934     
30935     /**
30936      * Returns true if the Operation has been started. Note that the Operation may have started AND completed,
30937      * see {@link #isRunning} to test if the Operation is currently running.
30938      * @return {Boolean} True if the Operation has started
30939      */
30940     isStarted: function() {
30941         return this.started === true;
30942     },
30943     
30944     /**
30945      * Returns true if the Operation has been started but has not yet completed.
30946      * @return {Boolean} True if the Operation is currently running
30947      */
30948     isRunning: function() {
30949         return this.running === true;
30950     },
30951     
30952     /**
30953      * Returns true if the Operation has been completed
30954      * @return {Boolean} True if the Operation is complete
30955      */
30956     isComplete: function() {
30957         return this.complete === true;
30958     },
30959     
30960     /**
30961      * Returns true if the Operation has completed and was successful
30962      * @return {Boolean} True if successful
30963      */
30964     wasSuccessful: function() {
30965         return this.isComplete() && this.success === true;
30966     },
30967     
30968     /**
30969      * @private
30970      * Associates this Operation with a Batch
30971      * @param {Ext.data.Batch} batch The batch
30972      */
30973     setBatch: function(batch) {
30974         this.batch = batch;
30975     },
30976     
30977     /**
30978      * Checks whether this operation should cause writing to occur.
30979      * @return {Boolean} Whether the operation should cause a write to occur.
30980      */
30981     allowWrite: function() {
30982         return this.action != 'read';
30983     }
30984 });
30985 /**
30986  * @author Ed Spencer
30987  * @class Ext.data.validations
30988  * @extends Object
30989  * 
30990  * <p>This singleton contains a set of validation functions that can be used to validate any type
30991  * of data. They are most often used in {@link Ext.data.Model Models}, where they are automatically
30992  * set up and executed.</p>
30993  */
30994 Ext.define('Ext.data.validations', {
30995     singleton: true,
30996     
30997     /**
30998      * The default error message used when a presence validation fails
30999      * @property presenceMessage
31000      * @type String
31001      */
31002     presenceMessage: 'must be present',
31003     
31004     /**
31005      * The default error message used when a length validation fails
31006      * @property lengthMessage
31007      * @type String
31008      */
31009     lengthMessage: 'is the wrong length',
31010     
31011     /**
31012      * The default error message used when a format validation fails
31013      * @property formatMessage
31014      * @type Boolean
31015      */
31016     formatMessage: 'is the wrong format',
31017     
31018     /**
31019      * The default error message used when an inclusion validation fails
31020      * @property inclusionMessage
31021      * @type String
31022      */
31023     inclusionMessage: 'is not included in the list of acceptable values',
31024     
31025     /**
31026      * The default error message used when an exclusion validation fails
31027      * @property exclusionMessage
31028      * @type String
31029      */
31030     exclusionMessage: 'is not an acceptable value',
31031     
31032     /**
31033      * Validates that the given value is present
31034      * @param {Object} config Optional config object
31035      * @param {Mixed} value The value to validate
31036      * @return {Boolean} True if validation passed
31037      */
31038     presence: function(config, value) {
31039         if (value === undefined) {
31040             value = config;
31041         }
31042         
31043         return !!value;
31044     },
31045     
31046     /**
31047      * Returns true if the given value is between the configured min and max values
31048      * @param {Object} config Optional config object
31049      * @param {String} value The value to validate
31050      * @return {Boolean} True if the value passes validation
31051      */
31052     length: function(config, value) {
31053         if (value === undefined) {
31054             return false;
31055         }
31056         
31057         var length = value.length,
31058             min    = config.min,
31059             max    = config.max;
31060         
31061         if ((min && length < min) || (max && length > max)) {
31062             return false;
31063         } else {
31064             return true;
31065         }
31066     },
31067     
31068     /**
31069      * Returns true if the given value passes validation against the configured {@link #matcher} regex
31070      * @param {Object} config Optional config object
31071      * @param {String} value The value to validate
31072      * @return {Boolean} True if the value passes the format validation
31073      */
31074     format: function(config, value) {
31075         return !!(config.matcher && config.matcher.test(value));
31076     },
31077     
31078     /**
31079      * Validates that the given value is present in the configured {@link #list}
31080      * @param {String} value The value to validate
31081      * @return {Boolean} True if the value is present in the list
31082      */
31083     inclusion: function(config, value) {
31084         return config.list && Ext.Array.indexOf(config.list,value) != -1;
31085     },
31086     
31087     /**
31088      * Validates that the given value is present in the configured {@link #list}
31089      * @param {Object} config Optional config object
31090      * @param {String} value The value to validate
31091      * @return {Boolean} True if the value is not present in the list
31092      */
31093     exclusion: function(config, value) {
31094         return config.list && Ext.Array.indexOf(config.list,value) == -1;
31095     }
31096 });
31097 /**
31098  * @author Ed Spencer
31099  * @class Ext.data.ResultSet
31100  * @extends Object
31101  * 
31102  * <p>Simple wrapper class that represents a set of records returned by a Proxy.</p>
31103  * 
31104  * @constructor
31105  * Creates the new ResultSet
31106  */
31107 Ext.define('Ext.data.ResultSet', {
31108     /**
31109      * @cfg {Boolean} loaded
31110      * True if the records have already been loaded. This is only meaningful when dealing with
31111      * SQL-backed proxies
31112      */
31113     loaded: true,
31114     
31115     /**
31116      * @cfg {Number} count
31117      * The number of records in this ResultSet. Note that total may differ from this number
31118      */
31119     count: 0,
31120     
31121     /**
31122      * @cfg {Number} total
31123      * The total number of records reported by the data source. This ResultSet may form a subset of
31124      * those records (see count)
31125      */
31126     total: 0,
31127     
31128     /**
31129      * @cfg {Boolean} success
31130      * True if the ResultSet loaded successfully, false if any errors were encountered
31131      */
31132     success: false,
31133     
31134     /**
31135      * @cfg {Array} records The array of record instances. Required
31136      */
31137
31138     constructor: function(config) {
31139         Ext.apply(this, config);
31140         
31141         /**
31142          * DEPRECATED - will be removed in Ext JS 5.0. This is just a copy of this.total - use that instead
31143          * @property totalRecords
31144          * @type Mixed
31145          */
31146         this.totalRecords = this.total;
31147         
31148         if (config.count === undefined) {
31149             this.count = this.records.length;
31150         }
31151     }
31152 });
31153 /**
31154  * @author Ed Spencer
31155  * @class Ext.data.writer.Writer
31156  * @extends Object
31157  * 
31158  * <p>Base Writer class used by most subclasses of {@link Ext.data.proxy.Server}. This class is
31159  * responsible for taking a set of {@link Ext.data.Operation} objects and a {@link Ext.data.Request}
31160  * object and modifying that request based on the Operations.</p>
31161  * 
31162  * <p>For example a Ext.data.writer.Json would format the Operations and their {@link Ext.data.Model} 
31163  * instances based on the config options passed to the JsonWriter's constructor.</p>
31164  * 
31165  * <p>Writers are not needed for any kind of local storage - whether via a
31166  * {@link Ext.data.proxy.WebStorage Web Storage proxy} (see {@link Ext.data.proxy.LocalStorage localStorage}
31167  * and {@link Ext.data.proxy.SessionStorage sessionStorage}) or just in memory via a
31168  * {@link Ext.data.proxy.Memory MemoryProxy}.</p>
31169  * 
31170  * @constructor
31171  * @param {Object} config Optional config object
31172  */
31173 Ext.define('Ext.data.writer.Writer', {
31174     alias: 'writer.base',
31175     alternateClassName: ['Ext.data.DataWriter', 'Ext.data.Writer'],
31176     
31177     /**
31178      * @cfg {Boolean} writeAllFields True to write all fields from the record to the server. If set to false it
31179      * will only send the fields that were modified. Defaults to <tt>true</tt>. Note that any fields that have
31180      * {@link Ext.data.Field#persist} set to false will still be ignored.
31181      */
31182     writeAllFields: true,
31183     
31184     /**
31185      * @cfg {String} nameProperty This property is used to read the key for each value that will be sent to the server.
31186      * For example:
31187      * <pre><code>
31188 Ext.define('Person', {
31189     extend: 'Ext.data.Model',
31190     fields: [{
31191         name: 'first',
31192         mapping: 'firstName'
31193     }, {
31194         name: 'last',
31195         mapping: 'lastName'
31196     }, {
31197         name: 'age'
31198     }]
31199 });
31200 new Ext.data.writer.Writer({
31201     writeAllFields: true,
31202     nameProperty: 'mapping'
31203 });
31204
31205 // This will be sent to the server
31206 {
31207     firstName: 'first name value',
31208     lastName: 'last name value',
31209     age: 1
31210 }
31211
31212      * </code></pre>
31213      * Defaults to <tt>name</tt>. If the value is not present, the field name will always be used.
31214      */
31215     nameProperty: 'name',
31216
31217     constructor: function(config) {
31218         Ext.apply(this, config);
31219     },
31220
31221     /**
31222      * Prepares a Proxy's Ext.data.Request object
31223      * @param {Ext.data.Request} request The request object
31224      * @return {Ext.data.Request} The modified request object
31225      */
31226     write: function(request) {
31227         var operation = request.operation,
31228             records   = operation.records || [],
31229             len       = records.length,
31230             i         = 0,
31231             data      = [];
31232
31233         for (; i < len; i++) {
31234             data.push(this.getRecordData(records[i]));
31235         }
31236         return this.writeRecords(request, data);
31237     },
31238
31239     /**
31240      * Formats the data for each record before sending it to the server. This
31241      * method should be overridden to format the data in a way that differs from the default.
31242      * @param {Object} record The record that we are writing to the server.
31243      * @return {Object} An object literal of name/value keys to be written to the server.
31244      * By default this method returns the data property on the record.
31245      */
31246     getRecordData: function(record) {
31247         var isPhantom = record.phantom === true,
31248             writeAll = this.writeAllFields || isPhantom,
31249             nameProperty = this.nameProperty,
31250             fields = record.fields,
31251             data = {},
31252             changes,
31253             name,
31254             field,
31255             key;
31256         
31257         if (writeAll) {
31258             fields.each(function(field){
31259                 if (field.persist) {
31260                     name = field[nameProperty] || field.name;
31261                     data[name] = record.get(field.name);
31262                 }
31263             });
31264         } else {
31265             // Only write the changes
31266             changes = record.getChanges();
31267             for (key in changes) {
31268                 if (changes.hasOwnProperty(key)) {
31269                     field = fields.get(key);
31270                     name = field[nameProperty] || field.name;
31271                     data[name] = changes[key];
31272                 }
31273             }
31274             if (!isPhantom) {
31275                 // always include the id for non phantoms
31276                 data[record.idProperty] = record.getId();
31277             }
31278         }
31279         return data;
31280     }
31281 });
31282
31283 /**
31284  * @class Ext.util.Floating
31285  * A mixin to add floating capability to a Component
31286  */
31287 Ext.define('Ext.util.Floating', {
31288
31289     uses: ['Ext.Layer', 'Ext.window.Window'],
31290
31291     /**
31292      * @cfg {Boolean} focusOnToFront
31293      * Specifies whether the floated component should be automatically {@link #focus focused} when it is
31294      * {@link #toFront brought to the front}. Defaults to true.
31295      */
31296     focusOnToFront: true,
31297
31298     /**
31299      * @cfg {String/Boolean} shadow Specifies whether the floating component should be given a shadow. Set to
31300      * <tt>true</tt> to automatically create an {@link Ext.Shadow}, or a string indicating the
31301      * shadow's display {@link Ext.Shadow#mode}. Set to <tt>false</tt> to disable the shadow.
31302      * (Defaults to <tt>'sides'</tt>.)
31303      */
31304     shadow: 'sides',
31305
31306     constructor: function(config) {
31307         this.floating = true;
31308         this.el = Ext.create('Ext.Layer', Ext.apply({}, config, {
31309             hideMode: this.hideMode,
31310             hidden: this.hidden,
31311             shadow: Ext.isDefined(this.shadow) ? this.shadow : 'sides',
31312             shadowOffset: this.shadowOffset,
31313             constrain: false,
31314             shim: this.shim === false ? false : undefined
31315         }), this.el);
31316     },
31317
31318     onFloatRender: function() {
31319         var me = this;
31320         me.zIndexParent = me.getZIndexParent();
31321         me.setFloatParent(me.ownerCt);
31322         delete me.ownerCt;
31323
31324         if (me.zIndexParent) {
31325             me.zIndexParent.registerFloatingItem(me);
31326         } else {
31327             Ext.WindowManager.register(me);
31328         }
31329     },
31330
31331     setFloatParent: function(floatParent) {
31332         var me = this;
31333
31334         // Remove listeners from previous floatParent
31335         if (me.floatParent) {
31336             me.mun(me.floatParent, {
31337                 hide: me.onFloatParentHide,
31338                 show: me.onFloatParentShow,
31339                 scope: me
31340             });
31341         }
31342
31343         me.floatParent = floatParent;
31344
31345         // Floating Components as children of Containers must hide when their parent hides.
31346         if (floatParent) {
31347             me.mon(me.floatParent, {
31348                 hide: me.onFloatParentHide,
31349                 show: me.onFloatParentShow,
31350                 scope: me
31351             });
31352         }
31353
31354         // If a floating Component is configured to be constrained, but has no configured
31355         // constrainTo setting, set its constrainTo to be it's ownerCt before rendering.
31356         if ((me.constrain || me.constrainHeader) && !me.constrainTo) {
31357             me.constrainTo = floatParent ? floatParent.getTargetEl() : me.container;
31358         }
31359     },
31360
31361     onFloatParentHide: function() {
31362         this.showOnParentShow = this.isVisible();
31363         this.hide();
31364     },
31365
31366     onFloatParentShow: function() {
31367         if (this.showOnParentShow) {
31368             delete this.showOnParentShow;
31369             this.show();
31370         }
31371     },
31372
31373     /**
31374      * @private
31375      * <p>Finds the ancestor Container responsible for allocating zIndexes for the passed Component.</p>
31376      * <p>That will be the outermost floating Container (a Container which has no ownerCt and has floating:true).</p>
31377      * <p>If we have no ancestors, or we walk all the way up to the document body, there's no zIndexParent,
31378      * and the global Ext.WindowManager will be used.</p>
31379      */
31380     getZIndexParent: function() {
31381         var p = this.ownerCt,
31382             c;
31383
31384         if (p) {
31385             while (p) {
31386                 c = p;
31387                 p = p.ownerCt;
31388             }
31389             if (c.floating) {
31390                 return c;
31391             }
31392         }
31393     },
31394
31395     // private
31396     // z-index is managed by the zIndexManager and may be overwritten at any time.
31397     // Returns the next z-index to be used.
31398     // If this is a Container, then it will have rebased any managed floating Components,
31399     // and so the next available z-index will be approximately 10000 above that.
31400     setZIndex: function(index) {
31401         var me = this;
31402         this.el.setZIndex(index);
31403
31404         // Next item goes 10 above;
31405         index += 10;
31406
31407         // When a Container with floating items has its z-index set, it rebases any floating items it is managing.
31408         // The returned value is a round number approximately 10000 above the last z-index used.
31409         if (me.floatingItems) {
31410             index = Math.floor(me.floatingItems.setBase(index) / 100) * 100 + 10000;
31411         }
31412         return index;
31413     },
31414
31415     /**
31416      * <p>Moves this floating Component into a constrain region.</p>
31417      * <p>By default, this Component is constrained to be within the container it was added to, or the element
31418      * it was rendered to.</p>
31419      * <p>An alternative constraint may be passed.</p>
31420      * @param {Mixed} constrainTo Optional. The Element or {@link Ext.util.Region Region} into which this Component is to be constrained.
31421      */
31422     doConstrain: function(constrainTo) {
31423         var me = this,
31424             constrainEl,
31425             vector,
31426             xy;
31427
31428         if (me.constrain || me.constrainHeader) {
31429             if (me.constrainHeader) {
31430                 constrainEl = me.header.el;
31431             } else {
31432                 constrainEl = me.el;
31433             }
31434             vector = constrainEl.getConstrainVector(constrainTo || (me.floatParent && me.floatParent.getTargetEl()) || me.container);
31435             if (vector) {
31436                 xy = me.getPosition();
31437                 xy[0] += vector[0];
31438                 xy[1] += vector[1];
31439                 me.setPosition(xy);
31440             }
31441         }
31442     },
31443
31444     /**
31445      * Aligns this floating Component to the specified element
31446      * @param {Mixed} element The element or {@link Ext.Component} to align to. If passing a component, it must
31447      * be a omponent instance. If a string id is passed, it will be used as an element id.
31448      * @param {String} position (optional, defaults to "tl-bl?") The position to align to (see {@link Ext.core.Element#alignTo} for more details).
31449      * @param {Array} offsets (optional) Offset the positioning by [x, y]
31450      * @return {Component} this
31451      */
31452     alignTo: function(element, position, offsets) {
31453         if (element.isComponent) {
31454             element = element.getEl();
31455         }
31456         var xy = this.el.getAlignToXY(element, position, offsets);
31457         this.setPagePosition(xy);
31458         return this;
31459     },
31460
31461     /**
31462      * <p>Brings this floating Component to the front of any other visible, floating Components managed by the same {@link Ext.ZIndexManager ZIndexManager}</p>
31463      * <p>If this Component is modal, inserts the modal mask just below this Component in the z-index stack.</p>
31464      * @param {Boolean} preventFocus (optional) Specify <code>true</code> to prevent the Component from being focused.
31465      * @return {Component} this
31466      */
31467     toFront: function(preventFocus) {
31468         var me = this;
31469
31470         // Find the floating Component which provides the base for this Component's zIndexing.
31471         // That must move to front to then be able to rebase its zIndex stack and move this to the front
31472         if (me.zIndexParent) {
31473             me.zIndexParent.toFront(true);
31474         }
31475         if (me.zIndexManager.bringToFront(me)) {
31476             if (!Ext.isDefined(preventFocus)) {
31477                 preventFocus = !me.focusOnToFront;
31478             }
31479             if (!preventFocus) {
31480                 // Kick off a delayed focus request.
31481                 // If another floating Component is toFronted before the delay expires
31482                 // this will not receive focus.
31483                 me.focus(false, true);
31484             }
31485         }
31486         return me;
31487     },
31488
31489     /**
31490      * <p>This method is called internally by {@link Ext.ZIndexManager} to signal that a floating
31491      * Component has either been moved to the top of its zIndex stack, or pushed from the top of its zIndex stack.</p>
31492      * <p>If a <i>Window</i> is superceded by another Window, deactivating it hides its shadow.</p>
31493      * <p>This method also fires the {@link #activate} or {@link #deactivate} event depending on which action occurred.</p>
31494      * @param {Boolean} active True to activate the Component, false to deactivate it (defaults to false)
31495      * @param {Component} newActive The newly active Component which is taking over topmost zIndex position.
31496      */
31497     setActive: function(active, newActive) {
31498         if (active) {
31499             if ((this instanceof Ext.window.Window) && !this.maximized) {
31500                 this.el.enableShadow(true);
31501             }
31502             this.fireEvent('activate', this);
31503         } else {
31504             // Only the *Windows* in a zIndex stack share a shadow. All other types of floaters
31505             // can keep their shadows all the time
31506             if ((this instanceof Ext.window.Window) && (newActive instanceof Ext.window.Window)) {
31507                 this.el.disableShadow();
31508             }
31509             this.fireEvent('deactivate', this);
31510         }
31511     },
31512
31513     /**
31514      * Sends this Component to the back of (lower z-index than) any other visible windows
31515      * @return {Component} this
31516      */
31517     toBack: function() {
31518         this.zIndexManager.sendToBack(this);
31519         return this;
31520     },
31521
31522     /**
31523      * Center this Component in its container.
31524      * @return {Component} this
31525      */
31526     center: function() {
31527         var xy = this.el.getAlignToXY(this.container, 'c-c');
31528         this.setPagePosition(xy);
31529         return this;
31530     },
31531
31532     // private
31533     syncShadow : function(){
31534         if (this.floating) {
31535             this.el.sync(true);
31536         }
31537     },
31538
31539     // private
31540     fitContainer: function() {
31541         var parent = this.floatParent,
31542             container = parent ? parent.getTargetEl() : this.container,
31543             size = container.getViewSize(false);
31544
31545         this.setSize(size);
31546     }
31547 });
31548 /**
31549  * @class Ext.layout.container.AbstractContainer
31550  * @extends Ext.layout.Layout
31551  * Please refer to sub classes documentation
31552  */
31553
31554 Ext.define('Ext.layout.container.AbstractContainer', {
31555
31556     /* Begin Definitions */
31557
31558     extend: 'Ext.layout.Layout',
31559
31560     /* End Definitions */
31561
31562     type: 'container',
31563
31564     fixedLayout: true,
31565
31566     // @private
31567     managedHeight: true,
31568     // @private
31569     managedWidth: true,
31570
31571     /**
31572      * @cfg {Boolean} bindToOwnerCtComponent
31573      * Flag to notify the ownerCt Component on afterLayout of a change
31574      */
31575     bindToOwnerCtComponent: false,
31576
31577     /**
31578      * @cfg {Boolean} bindToOwnerCtContainer
31579      * Flag to notify the ownerCt Container on afterLayout of a change
31580      */
31581     bindToOwnerCtContainer: false,
31582
31583     /**
31584      * @cfg {String} itemCls
31585      * <p>An optional extra CSS class that will be added to the container. This can be useful for adding
31586      * customized styles to the container or any of its children using standard CSS rules. See
31587      * {@link Ext.Component}.{@link Ext.Component#ctCls ctCls} also.</p>
31588      * </p>
31589      */
31590
31591     isManaged: function(dimension) {
31592         dimension = Ext.String.capitalize(dimension);
31593         var me = this,
31594             child = me,
31595             managed = me['managed' + dimension],
31596             ancestor = me.owner.ownerCt;
31597
31598         if (ancestor && ancestor.layout) {
31599             while (ancestor && ancestor.layout) {
31600                 if (managed === false || ancestor.layout['managed' + dimension] === false) {
31601                     managed = false;
31602                     break;
31603                 }
31604                 ancestor = ancestor.ownerCt;
31605             }
31606         }
31607         return managed;
31608     },
31609
31610     layout: function() {
31611         var me = this,
31612             owner = me.owner;
31613         if (Ext.isNumber(owner.height) || owner.isViewport) {
31614             me.managedHeight = false;
31615         }
31616         if (Ext.isNumber(owner.width) || owner.isViewport) {
31617             me.managedWidth = false;
31618         }
31619         me.callParent(arguments);
31620     },
31621
31622     /**
31623     * Set the size of an item within the Container.  We should always use setCalculatedSize.
31624     * @private
31625     */
31626     setItemSize: function(item, width, height) {
31627         if (Ext.isObject(width)) {
31628             height = width.height;
31629             width = width.width;
31630         }
31631         item.setCalculatedSize(width, height, this.owner);
31632     },
31633
31634     /**
31635      * <p>Returns an array of child components either for a render phase (Performed in the beforeLayout method of the layout's
31636      * base class), or the layout phase (onLayout).</p>
31637      * @return {Array} of child components
31638      */
31639     getLayoutItems: function() {
31640         return this.owner && this.owner.items && this.owner.items.items || [];
31641     },
31642
31643     afterLayout: function() {
31644         this.owner.afterLayout(this);
31645     },
31646     /**
31647      * Returns the owner component's resize element.
31648      * @return {Ext.core.Element}
31649      */
31650      getTarget: function() {
31651          return this.owner.getTargetEl();
31652      },
31653     /**
31654      * <p>Returns the element into which rendering must take place. Defaults to the owner Container's {@link Ext.AbstractComponent#targetEl}.</p>
31655      * May be overridden in layout managers which implement an inner element.
31656      * @return {Ext.core.Element}
31657      */
31658      getRenderTarget: function() {
31659          return this.owner.getTargetEl();
31660      }
31661 });
31662
31663 /**
31664  * @class Ext.ZIndexManager
31665  * <p>A class that manages a group of {@link Ext.Component#floating} Components and provides z-order management,
31666  * and Component activation behavior, including masking below the active (topmost) Component.</p>
31667  * <p>{@link Ext.Component#floating Floating} Components which are rendered directly into the document (Such as {@link Ext.window.Window Window}s which are
31668  * {@link Ext.Component#show show}n are managed by a {@link Ext.WindowManager global instance}.</p>
31669  * <p>{@link Ext.Component#floating Floating} Components which are descendants of {@link Ext.Component#floating floating} <i>Containers</i>
31670  * (For example a {Ext.view.BoundList BoundList} within an {@link Ext.window.Window Window}, or a {@link Ext.menu.Menu Menu}),
31671  * are managed by a ZIndexManager owned by that floating Container. So ComboBox dropdowns within Windows will have managed z-indices
31672  * guaranteed to be correct, relative to the Window.</p>
31673  * @constructor
31674  */
31675 Ext.define('Ext.ZIndexManager', {
31676
31677     alternateClassName: 'Ext.WindowGroup',
31678
31679     statics: {
31680         zBase : 9000
31681     },
31682
31683     constructor: function(container) {
31684         var me = this;
31685
31686         me.list = {};
31687         me.zIndexStack = [];
31688         me.front = null;
31689
31690         if (container) {
31691
31692             // This is the ZIndexManager for an Ext.container.Container, base its zseed on the zIndex of the Container's element
31693             if (container.isContainer) {
31694                 container.on('resize', me._onContainerResize, me);
31695                 me.zseed = Ext.Number.from(container.getEl().getStyle('zIndex'), me.getNextZSeed());
31696                 // The containing element we will be dealing with (eg masking) is the content target
31697                 me.targetEl = container.getTargetEl();
31698                 me.container = container;
31699             }
31700             // This is the ZIndexManager for a DOM element
31701             else {
31702                 Ext.EventManager.onWindowResize(me._onContainerResize, me);
31703                 me.zseed = me.getNextZSeed();
31704                 me.targetEl = Ext.get(container);
31705             }
31706         }
31707         // No container passed means we are the global WindowManager. Our target is the doc body.
31708         // DOM must be ready to collect that ref.
31709         else {
31710             Ext.EventManager.onWindowResize(me._onContainerResize, me);
31711             me.zseed = me.getNextZSeed();
31712             Ext.onDocumentReady(function() {
31713                 me.targetEl = Ext.getBody();
31714             });
31715         }
31716     },
31717
31718     getNextZSeed: function() {
31719         return (Ext.ZIndexManager.zBase += 10000);
31720     },
31721
31722     setBase: function(baseZIndex) {
31723         this.zseed = baseZIndex;
31724         return this.assignZIndices();
31725     },
31726
31727     // private
31728     assignZIndices: function() {
31729         var a = this.zIndexStack,
31730             len = a.length,
31731             i = 0,
31732             zIndex = this.zseed,
31733             comp;
31734
31735         for (; i < len; i++) {
31736             comp = a[i];
31737             if (comp && !comp.hidden) {
31738
31739                 // Setting the zIndex of a Component returns the topmost zIndex consumed by
31740                 // that Component.
31741                 // If it's just a plain floating Component such as a BoundList, then the
31742                 // return value is the passed value plus 10, ready for the next item.
31743                 // If a floating *Container* has its zIndex set, it re-orders its managed
31744                 // floating children, starting from that new base, and returns a value 10000 above
31745                 // the highest zIndex which it allocates.
31746                 zIndex = comp.setZIndex(zIndex);
31747             }
31748         }
31749         this._activateLast();
31750         return zIndex;
31751     },
31752
31753     // private
31754     _setActiveChild: function(comp) {
31755         if (comp != this.front) {
31756
31757             if (this.front) {
31758                 this.front.setActive(false, comp);
31759             }
31760             this.front = comp;
31761             if (comp) {
31762                 comp.setActive(true);
31763                 if (comp.modal) {
31764                     this._showModalMask(comp.el.getStyle('zIndex') - 4);
31765                 }
31766             }
31767         }
31768     },
31769
31770     // private
31771     _activateLast: function(justHidden) {
31772         var comp,
31773             lastActivated = false,
31774             i;
31775
31776         // Go down through the z-index stack.
31777         // Activate the next visible one down.
31778         // Keep going down to find the next visible modal one to shift the modal mask down under
31779         for (i = this.zIndexStack.length-1; i >= 0; --i) {
31780             comp = this.zIndexStack[i];
31781             if (!comp.hidden) {
31782                 if (!lastActivated) {
31783                     this._setActiveChild(comp);
31784                     lastActivated = true;
31785                 }
31786
31787                 // Move any modal mask down to just under the next modal floater down the stack
31788                 if (comp.modal) {
31789                     this._showModalMask(comp.el.getStyle('zIndex') - 4);
31790                     return;
31791                 }
31792             }
31793         }
31794
31795         // none to activate, so there must be no modal mask.
31796         // And clear the currently active property
31797         this._hideModalMask();
31798         if (!lastActivated) {
31799             this._setActiveChild(null);
31800         }
31801     },
31802
31803     _showModalMask: function(zIndex) {
31804         if (!this.mask) {
31805             this.mask = this.targetEl.createChild({
31806                 cls: Ext.baseCSSPrefix + 'mask'
31807             });
31808             this.mask.setVisibilityMode(Ext.core.Element.DISPLAY);
31809             this.mask.on('click', this._onMaskClick, this);
31810         }
31811         Ext.getBody().addCls(Ext.baseCSSPrefix + 'body-masked');
31812         this.mask.setSize(this.targetEl.getViewSize(true));
31813         this.mask.setStyle('zIndex', zIndex);
31814         this.mask.show();
31815     },
31816
31817     _hideModalMask: function() {
31818         if (this.mask) {
31819             Ext.getBody().removeCls(Ext.baseCSSPrefix + 'body-masked');
31820             this.mask.hide();
31821         }
31822     },
31823
31824     _onMaskClick: function() {
31825         if (this.front) {
31826             this.front.focus();
31827         }
31828     },
31829
31830     _onContainerResize: function() {
31831         if (this.mask && this.mask.isVisible()) {
31832             this.mask.setSize(this.targetEl.getViewSize(true));
31833         }
31834     },
31835
31836     /**
31837      * <p>Registers a floating {@link Ext.Component} with this ZIndexManager. This should not
31838      * need to be called under normal circumstances. Floating Components (such as Windows, BoundLists and Menus) are automatically registered
31839      * with a {@link Ext.Component#zIndexManager zIndexManager} at render time.</p>
31840      * <p>Where this may be useful is moving Windows between two ZIndexManagers. For example,
31841      * to bring the Ext.MessageBox dialog under the same manager as the Desktop's
31842      * ZIndexManager in the desktop sample app:</p><code><pre>
31843 MyDesktop.getDesktop().getManager().register(Ext.MessageBox);
31844 </pre></code>
31845      * @param {Component} comp The Component to register.
31846      */
31847     register : function(comp) {
31848         if (comp.zIndexManager) {
31849             comp.zIndexManager.unregister(comp);
31850         }
31851         comp.zIndexManager = this;
31852
31853         this.list[comp.id] = comp;
31854         this.zIndexStack.push(comp);
31855         comp.on('hide', this._activateLast, this);
31856     },
31857
31858     /**
31859      * <p>Unregisters a {@link Ext.Component} from this ZIndexManager. This should not
31860      * need to be called. Components are automatically unregistered upon destruction.
31861      * See {@link #register}.</p>
31862      * @param {Component} comp The Component to unregister.
31863      */
31864     unregister : function(comp) {
31865         delete comp.zIndexManager;
31866         if (this.list && this.list[comp.id]) {
31867             delete this.list[comp.id];
31868             comp.un('hide', this._activateLast);
31869             Ext.Array.remove(this.zIndexStack, comp);
31870
31871             // Destruction requires that the topmost visible floater be activated. Same as hiding.
31872             this._activateLast(comp);
31873         }
31874     },
31875
31876     /**
31877      * Gets a registered Component by id.
31878      * @param {String/Object} id The id of the Component or a {@link Ext.Component} instance
31879      * @return {Ext.Component}
31880      */
31881     get : function(id) {
31882         return typeof id == "object" ? id : this.list[id];
31883     },
31884
31885    /**
31886      * Brings the specified Component to the front of any other active Components in this ZIndexManager.
31887      * @param {String/Object} comp The id of the Component or a {@link Ext.Component} instance
31888      * @return {Boolean} True if the dialog was brought to the front, else false
31889      * if it was already in front
31890      */
31891     bringToFront : function(comp) {
31892         comp = this.get(comp);
31893         if (comp != this.front) {
31894             Ext.Array.remove(this.zIndexStack, comp);
31895             this.zIndexStack.push(comp);
31896             this.assignZIndices();
31897             return true;
31898         }
31899         if (comp.modal) {
31900             Ext.getBody().addCls(Ext.baseCSSPrefix + 'body-masked');
31901             this.mask.setSize(Ext.core.Element.getViewWidth(true), Ext.core.Element.getViewHeight(true));
31902             this.mask.show();
31903         }
31904         return false;
31905     },
31906
31907     /**
31908      * Sends the specified Component to the back of other active Components in this ZIndexManager.
31909      * @param {String/Object} comp The id of the Component or a {@link Ext.Component} instance
31910      * @return {Ext.Component} The Component
31911      */
31912     sendToBack : function(comp) {
31913         comp = this.get(comp);
31914         Ext.Array.remove(this.zIndexStack, comp);
31915         this.zIndexStack.unshift(comp);
31916         this.assignZIndices();
31917         return comp;
31918     },
31919
31920     /**
31921      * Hides all Components managed by this ZIndexManager.
31922      */
31923     hideAll : function() {
31924         for (var id in this.list) {
31925             if (this.list[id].isComponent && this.list[id].isVisible()) {
31926                 this.list[id].hide();
31927             }
31928         }
31929     },
31930
31931     /**
31932      * @private
31933      * Temporarily hides all currently visible managed Components. This is for when
31934      * dragging a Window which may manage a set of floating descendants in its ZIndexManager;
31935      * they should all be hidden just for the duration of the drag.
31936      */
31937     hide: function() {
31938         var i = 0,
31939             ln = this.zIndexStack.length,
31940             comp;
31941
31942         this.tempHidden = [];
31943         for (; i < ln; i++) {
31944             comp = this.zIndexStack[i];
31945             if (comp.isVisible()) {
31946                 this.tempHidden.push(comp);
31947                 comp.hide();
31948             }
31949         }
31950     },
31951
31952     /**
31953      * @private
31954      * Restores temporarily hidden managed Components to visibility.
31955      */
31956     show: function() {
31957         var i = 0,
31958             ln = this.tempHidden.length,
31959             comp,
31960             x,
31961             y;
31962
31963         for (; i < ln; i++) {
31964             comp = this.tempHidden[i];
31965             x = comp.x;
31966             y = comp.y;
31967             comp.show();
31968             comp.setPosition(x, y);
31969         }
31970         delete this.tempHidden;
31971     },
31972
31973     /**
31974      * Gets the currently-active Component in this ZIndexManager.
31975      * @return {Ext.Component} The active Component
31976      */
31977     getActive : function() {
31978         return this.front;
31979     },
31980
31981     /**
31982      * Returns zero or more Components in this ZIndexManager using the custom search function passed to this method.
31983      * The function should accept a single {@link Ext.Component} reference as its only argument and should
31984      * return true if the Component matches the search criteria, otherwise it should return false.
31985      * @param {Function} fn The search function
31986      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the Component being tested.
31987      * that gets passed to the function if not specified)
31988      * @return {Array} An array of zero or more matching windows
31989      */
31990     getBy : function(fn, scope) {
31991         var r = [],
31992             i = 0,
31993             len = this.zIndexStack.length,
31994             comp;
31995
31996         for (; i < len; i++) {
31997             comp = this.zIndexStack[i];
31998             if (fn.call(scope||comp, comp) !== false) {
31999                 r.push(comp);
32000             }
32001         }
32002         return r;
32003     },
32004
32005     /**
32006      * Executes the specified function once for every Component in this ZIndexManager, passing each
32007      * Component as the only parameter. Returning false from the function will stop the iteration.
32008      * @param {Function} fn The function to execute for each item
32009      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current Component in the iteration.
32010      */
32011     each : function(fn, scope) {
32012         var comp;
32013         for (var id in this.list) {
32014             comp = this.list[id];
32015             if (comp.isComponent && fn.call(scope || comp, comp) === false) {
32016                 return;
32017             }
32018         }
32019     },
32020
32021     /**
32022      * Executes the specified function once for every Component in this ZIndexManager, passing each
32023      * Component as the only parameter. Returning false from the function will stop the iteration.
32024      * The components are passed to the function starting at the bottom and proceeding to the top.
32025      * @param {Function} fn The function to execute for each item
32026      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function
32027      * is executed. Defaults to the current Component in the iteration.
32028      */
32029     eachBottomUp: function (fn, scope) {
32030         var comp,
32031             stack = this.zIndexStack,
32032             i, n;
32033
32034         for (i = 0, n = stack.length ; i < n; i++) {
32035             comp = stack[i];
32036             if (comp.isComponent && fn.call(scope || comp, comp) === false) {
32037                 return;
32038             }
32039         }
32040     },
32041
32042     /**
32043      * Executes the specified function once for every Component in this ZIndexManager, passing each
32044      * Component as the only parameter. Returning false from the function will stop the iteration.
32045      * The components are passed to the function starting at the top and proceeding to the bottom.
32046      * @param {Function} fn The function to execute for each item
32047      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function
32048      * is executed. Defaults to the current Component in the iteration.
32049      */
32050     eachTopDown: function (fn, scope) {
32051         var comp,
32052             stack = this.zIndexStack,
32053             i;
32054
32055         for (i = stack.length ; i-- > 0; ) {
32056             comp = stack[i];
32057             if (comp.isComponent && fn.call(scope || comp, comp) === false) {
32058                 return;
32059             }
32060         }
32061     },
32062
32063     destroy: function() {
32064         delete this.zIndexStack;
32065         delete this.list;
32066         delete this.container;
32067         delete this.targetEl;
32068     }
32069 }, function() {
32070     /**
32071      * @class Ext.WindowManager
32072      * @extends Ext.ZIndexManager
32073      * <p>The default global floating Component group that is available automatically.</p>
32074      * <p>This manages instances of floating Components which were rendered programatically without
32075      * being added to a {@link Ext.container.Container Container}, and for floating Components which were added into non-floating Containers.</p>
32076      * <p><i>Floating</i> Containers create their own instance of ZIndexManager, and floating Components added at any depth below
32077      * there are managed by that ZIndexManager.</p>
32078      * @singleton
32079      */
32080     Ext.WindowManager = Ext.WindowMgr = new this();
32081 });
32082
32083 /**
32084  * @class Ext.layout.container.boxOverflow.None
32085  * @extends Object
32086  * @private
32087  * Base class for Box Layout overflow handlers. These specialized classes are invoked when a Box Layout
32088  * (either an HBox or a VBox) has child items that are either too wide (for HBox) or too tall (for VBox)
32089  * for its container.
32090  */
32091 Ext.define('Ext.layout.container.boxOverflow.None', {
32092     
32093     alternateClassName: 'Ext.layout.boxOverflow.None',
32094     
32095     constructor: function(layout, config) {
32096         this.layout = layout;
32097         Ext.apply(this, config || {});
32098     },
32099
32100     handleOverflow: Ext.emptyFn,
32101
32102     clearOverflow: Ext.emptyFn,
32103
32104     /**
32105      * @private
32106      * Normalizes an item reference, string id or numerical index into a reference to the item
32107      * @param {Ext.Component|String|Number} item The item reference, id or index
32108      * @return {Ext.Component} The item
32109      */
32110     getItem: function(item) {
32111         return this.layout.owner.getComponent(item);
32112     }
32113 });
32114 /**
32115  * @class Ext.util.KeyMap
32116  * Handles mapping keys to actions for an element. One key map can be used for multiple actions.
32117  * The constructor accepts the same config object as defined by {@link #addBinding}.
32118  * If you bind a callback function to a KeyMap, anytime the KeyMap handles an expected key
32119  * combination it will call the function with this signature (if the match is a multi-key
32120  * combination the callback will still be called only once): (String key, Ext.EventObject e)
32121  * A KeyMap can also handle a string representation of keys.<br />
32122  * Usage:
32123  <pre><code>
32124 // map one key by key code
32125 var map = new Ext.util.KeyMap("my-element", {
32126     key: 13, // or Ext.EventObject.ENTER
32127     fn: myHandler,
32128     scope: myObject
32129 });
32130
32131 // map multiple keys to one action by string
32132 var map = new Ext.util.KeyMap("my-element", {
32133     key: "a\r\n\t",
32134     fn: myHandler,
32135     scope: myObject
32136 });
32137
32138 // map multiple keys to multiple actions by strings and array of codes
32139 var map = new Ext.util.KeyMap("my-element", [
32140     {
32141         key: [10,13],
32142         fn: function(){ alert("Return was pressed"); }
32143     }, {
32144         key: "abc",
32145         fn: function(){ alert('a, b or c was pressed'); }
32146     }, {
32147         key: "\t",
32148         ctrl:true,
32149         shift:true,
32150         fn: function(){ alert('Control + shift + tab was pressed.'); }
32151     }
32152 ]);
32153 </code></pre>
32154  * <b>Note: A KeyMap starts enabled</b>
32155  * @constructor
32156  * @param {Mixed} el The element to bind to
32157  * @param {Object} binding The binding (see {@link #addBinding})
32158  * @param {String} eventName (optional) The event to bind to (defaults to "keydown")
32159  */
32160 Ext.define('Ext.util.KeyMap', {
32161     alternateClassName: 'Ext.KeyMap',
32162     
32163     constructor: function(el, binding, eventName){
32164         var me = this;
32165         
32166         Ext.apply(me, {
32167             el: Ext.get(el),
32168             eventName: eventName || me.eventName,
32169             bindings: []
32170         });
32171         if (binding) {
32172             me.addBinding(binding);
32173         }
32174         me.enable();
32175     },
32176     
32177     eventName: 'keydown',
32178
32179     /**
32180      * Add a new binding to this KeyMap. The following config object properties are supported:
32181      * <pre>
32182 Property            Type             Description
32183 ----------          ---------------  ----------------------------------------------------------------------
32184 key                 String/Array     A single keycode or an array of keycodes to handle
32185 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)
32186 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)
32187 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)
32188 handler             Function         The function to call when KeyMap finds the expected key combination
32189 fn                  Function         Alias of handler (for backwards-compatibility)
32190 scope               Object           The scope of the callback function
32191 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. 
32192 </pre>
32193      *
32194      * Usage:
32195      * <pre><code>
32196 // Create a KeyMap
32197 var map = new Ext.util.KeyMap(document, {
32198     key: Ext.EventObject.ENTER,
32199     fn: handleKey,
32200     scope: this
32201 });
32202
32203 //Add a new binding to the existing KeyMap later
32204 map.addBinding({
32205     key: 'abc',
32206     shift: true,
32207     fn: handleKey,
32208     scope: this
32209 });
32210 </code></pre>
32211      * @param {Object/Array} binding A single KeyMap config or an array of configs
32212      */
32213     addBinding : function(binding){
32214         if (Ext.isArray(binding)) {
32215             Ext.each(binding, this.addBinding, this);
32216             return;
32217         }
32218         
32219         var keyCode = binding.key,
32220             processed = false,
32221             key,
32222             keys,
32223             keyString,
32224             i,
32225             len;
32226
32227         if (Ext.isString(keyCode)) {
32228             keys = [];
32229             keyString = keyCode.toLowerCase();
32230             
32231             for (i = 0, len = keyString.length; i < len; ++i){
32232                 keys.push(keyString.charCodeAt(i));
32233             }
32234             keyCode = keys;
32235             processed = true;
32236         }
32237         
32238         if (!Ext.isArray(keyCode)) {
32239             keyCode = [keyCode];
32240         }
32241         
32242         if (!processed) {
32243             for (i = 0, len = keyCode.length; i < len; ++i) {
32244                 key = keyCode[i];
32245                 if (Ext.isString(key)) {
32246                     keyCode[i] = key.toLowerCase().charCodeAt(0);
32247                 }
32248             }
32249         }
32250         
32251         this.bindings.push(Ext.apply({
32252             keyCode: keyCode
32253         }, binding));
32254     },
32255     
32256     /**
32257      * Process any keydown events on the element
32258      * @private
32259      * @param {Ext.EventObject} event
32260      */
32261     handleKeyDown: function(event) {
32262         if (this.enabled) { //just in case
32263             var bindings = this.bindings,
32264                 i = 0,
32265                 len = bindings.length;
32266                 
32267             event = this.processEvent(event);
32268             for(; i < len; ++i){
32269                 this.processBinding(bindings[i], event);
32270             }
32271         }
32272     },
32273     
32274     /**
32275      * Ugly hack to allow this class to be tested. Currently WebKit gives
32276      * no way to raise a key event properly with both
32277      * a) A keycode
32278      * b) The alt/ctrl/shift modifiers
32279      * So we have to simulate them here. Yuk! 
32280      * This is a stub method intended to be overridden by tests.
32281      * More info: https://bugs.webkit.org/show_bug.cgi?id=16735
32282      * @private
32283      */
32284     processEvent: function(event){
32285         return event;
32286     },
32287     
32288     /**
32289      * Process a particular binding and fire the handler if necessary.
32290      * @private
32291      * @param {Object} binding The binding information
32292      * @param {Ext.EventObject} event
32293      */
32294     processBinding: function(binding, event){
32295         if (this.checkModifiers(binding, event)) {
32296             var key = event.getKey(),
32297                 handler = binding.fn || binding.handler,
32298                 scope = binding.scope || this,
32299                 keyCode = binding.keyCode,
32300                 defaultEventAction = binding.defaultEventAction,
32301                 i,
32302                 len,
32303                 keydownEvent = new Ext.EventObjectImpl(event);
32304                 
32305             
32306             for (i = 0, len = keyCode.length; i < len; ++i) {
32307                 if (key === keyCode[i]) {
32308                     if (handler.call(scope, key, event) !== true && defaultEventAction) {
32309                         keydownEvent[defaultEventAction]();
32310                     }
32311                     break;
32312                 }
32313             }
32314         }
32315     },
32316     
32317     /**
32318      * Check if the modifiers on the event match those on the binding
32319      * @private
32320      * @param {Object} binding
32321      * @param {Ext.EventObject} event
32322      * @return {Boolean} True if the event matches the binding
32323      */
32324     checkModifiers: function(binding, e){
32325         var keys = ['shift', 'ctrl', 'alt'],
32326             i = 0,
32327             len = keys.length,
32328             val, key;
32329             
32330         for (; i < len; ++i){
32331             key = keys[i];
32332             val = binding[key];
32333             if (!(val === undefined || (val === e[key + 'Key']))) {
32334                 return false;
32335             }
32336         }
32337         return true;
32338     },
32339
32340     /**
32341      * Shorthand for adding a single key listener
32342      * @param {Number/Array/Object} key Either the numeric key code, array of key codes or an object with the
32343      * following options:
32344      * {key: (number or array), shift: (true/false), ctrl: (true/false), alt: (true/false)}
32345      * @param {Function} fn The function to call
32346      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the browser window.
32347      */
32348     on: function(key, fn, scope) {
32349         var keyCode, shift, ctrl, alt;
32350         if (Ext.isObject(key) && !Ext.isArray(key)) {
32351             keyCode = key.key;
32352             shift = key.shift;
32353             ctrl = key.ctrl;
32354             alt = key.alt;
32355         } else {
32356             keyCode = key;
32357         }
32358         this.addBinding({
32359             key: keyCode,
32360             shift: shift,
32361             ctrl: ctrl,
32362             alt: alt,
32363             fn: fn,
32364             scope: scope
32365         });
32366     },
32367
32368     /**
32369      * Returns true if this KeyMap is enabled
32370      * @return {Boolean}
32371      */
32372     isEnabled : function(){
32373         return this.enabled;
32374     },
32375
32376     /**
32377      * Enables this KeyMap
32378      */
32379     enable: function(){
32380         if(!this.enabled){
32381             this.el.on(this.eventName, this.handleKeyDown, this);
32382             this.enabled = true;
32383         }
32384     },
32385
32386     /**
32387      * Disable this KeyMap
32388      */
32389     disable: function(){
32390         if(this.enabled){
32391             this.el.removeListener(this.eventName, this.handleKeyDown, this);
32392             this.enabled = false;
32393         }
32394     },
32395
32396     /**
32397      * Convenience function for setting disabled/enabled by boolean.
32398      * @param {Boolean} disabled
32399      */
32400     setDisabled : function(disabled){
32401         if (disabled) {
32402             this.disable();
32403         } else {
32404             this.enable();
32405         }
32406     },
32407     
32408     /**
32409      * Destroys the KeyMap instance and removes all handlers.
32410      * @param {Boolean} removeEl True to also remove the attached element
32411      */
32412     destroy: function(removeEl){
32413         var me = this;
32414         
32415         me.bindings = [];
32416         me.disable();
32417         if (removeEl === true) {
32418             me.el.remove();
32419         }
32420         delete me.el;
32421     }
32422 });
32423 /**
32424  * @class Ext.util.ClickRepeater
32425  * @extends Ext.util.Observable
32426  *
32427  * A wrapper class which can be applied to any element. Fires a "click" event while the
32428  * mouse is pressed. The interval between firings may be specified in the config but
32429  * defaults to 20 milliseconds.
32430  *
32431  * Optionally, a CSS class may be applied to the element during the time it is pressed.
32432  *
32433  * @constructor
32434  * @param {Mixed} el The element to listen on
32435  * @param {Object} config
32436  */
32437
32438 Ext.define('Ext.util.ClickRepeater', {
32439     extend: 'Ext.util.Observable',
32440
32441     constructor : function(el, config){
32442         this.el = Ext.get(el);
32443         this.el.unselectable();
32444
32445         Ext.apply(this, config);
32446
32447         this.addEvents(
32448         /**
32449          * @event mousedown
32450          * Fires when the mouse button is depressed.
32451          * @param {Ext.util.ClickRepeater} this
32452          * @param {Ext.EventObject} e
32453          */
32454         "mousedown",
32455         /**
32456          * @event click
32457          * Fires on a specified interval during the time the element is pressed.
32458          * @param {Ext.util.ClickRepeater} this
32459          * @param {Ext.EventObject} e
32460          */
32461         "click",
32462         /**
32463          * @event mouseup
32464          * Fires when the mouse key is released.
32465          * @param {Ext.util.ClickRepeater} this
32466          * @param {Ext.EventObject} e
32467          */
32468         "mouseup"
32469         );
32470
32471         if(!this.disabled){
32472             this.disabled = true;
32473             this.enable();
32474         }
32475
32476         // allow inline handler
32477         if(this.handler){
32478             this.on("click", this.handler,  this.scope || this);
32479         }
32480
32481         this.callParent();
32482     },
32483
32484     /**
32485      * @cfg {Mixed} el The element to act as a button.
32486      */
32487
32488     /**
32489      * @cfg {String} pressedCls A CSS class name to be applied to the element while pressed.
32490      */
32491
32492     /**
32493      * @cfg {Boolean} accelerate True if autorepeating should start slowly and accelerate.
32494      * "interval" and "delay" are ignored.
32495      */
32496
32497     /**
32498      * @cfg {Number} interval The interval between firings of the "click" event. Default 20 ms.
32499      */
32500     interval : 20,
32501
32502     /**
32503      * @cfg {Number} delay The initial delay before the repeating event begins firing.
32504      * Similar to an autorepeat key delay.
32505      */
32506     delay: 250,
32507
32508     /**
32509      * @cfg {Boolean} preventDefault True to prevent the default click event
32510      */
32511     preventDefault : true,
32512     /**
32513      * @cfg {Boolean} stopDefault True to stop the default click event
32514      */
32515     stopDefault : false,
32516
32517     timer : 0,
32518
32519     /**
32520      * Enables the repeater and allows events to fire.
32521      */
32522     enable: function(){
32523         if(this.disabled){
32524             this.el.on('mousedown', this.handleMouseDown, this);
32525             if (Ext.isIE){
32526                 this.el.on('dblclick', this.handleDblClick, this);
32527             }
32528             if(this.preventDefault || this.stopDefault){
32529                 this.el.on('click', this.eventOptions, this);
32530             }
32531         }
32532         this.disabled = false;
32533     },
32534
32535     /**
32536      * Disables the repeater and stops events from firing.
32537      */
32538     disable: function(/* private */ force){
32539         if(force || !this.disabled){
32540             clearTimeout(this.timer);
32541             if(this.pressedCls){
32542                 this.el.removeCls(this.pressedCls);
32543             }
32544             Ext.getDoc().un('mouseup', this.handleMouseUp, this);
32545             this.el.removeAllListeners();
32546         }
32547         this.disabled = true;
32548     },
32549
32550     /**
32551      * Convenience function for setting disabled/enabled by boolean.
32552      * @param {Boolean} disabled
32553      */
32554     setDisabled: function(disabled){
32555         this[disabled ? 'disable' : 'enable']();
32556     },
32557
32558     eventOptions: function(e){
32559         if(this.preventDefault){
32560             e.preventDefault();
32561         }
32562         if(this.stopDefault){
32563             e.stopEvent();
32564         }
32565     },
32566
32567     // private
32568     destroy : function() {
32569         this.disable(true);
32570         Ext.destroy(this.el);
32571         this.clearListeners();
32572     },
32573
32574     handleDblClick : function(e){
32575         clearTimeout(this.timer);
32576         this.el.blur();
32577
32578         this.fireEvent("mousedown", this, e);
32579         this.fireEvent("click", this, e);
32580     },
32581
32582     // private
32583     handleMouseDown : function(e){
32584         clearTimeout(this.timer);
32585         this.el.blur();
32586         if(this.pressedCls){
32587             this.el.addCls(this.pressedCls);
32588         }
32589         this.mousedownTime = new Date();
32590
32591         Ext.getDoc().on("mouseup", this.handleMouseUp, this);
32592         this.el.on("mouseout", this.handleMouseOut, this);
32593
32594         this.fireEvent("mousedown", this, e);
32595         this.fireEvent("click", this, e);
32596
32597         // Do not honor delay or interval if acceleration wanted.
32598         if (this.accelerate) {
32599             this.delay = 400;
32600         }
32601
32602         // Re-wrap the event object in a non-shared object, so it doesn't lose its context if
32603         // the global shared EventObject gets a new Event put into it before the timer fires.
32604         e = new Ext.EventObjectImpl(e);
32605
32606         this.timer =  Ext.defer(this.click, this.delay || this.interval, this, [e]);
32607     },
32608
32609     // private
32610     click : function(e){
32611         this.fireEvent("click", this, e);
32612         this.timer =  Ext.defer(this.click, this.accelerate ?
32613             this.easeOutExpo(Ext.Date.getElapsed(this.mousedownTime),
32614                 400,
32615                 -390,
32616                 12000) :
32617             this.interval, this, [e]);
32618     },
32619
32620     easeOutExpo : function (t, b, c, d) {
32621         return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
32622     },
32623
32624     // private
32625     handleMouseOut : function(){
32626         clearTimeout(this.timer);
32627         if(this.pressedCls){
32628             this.el.removeCls(this.pressedCls);
32629         }
32630         this.el.on("mouseover", this.handleMouseReturn, this);
32631     },
32632
32633     // private
32634     handleMouseReturn : function(){
32635         this.el.un("mouseover", this.handleMouseReturn, this);
32636         if(this.pressedCls){
32637             this.el.addCls(this.pressedCls);
32638         }
32639         this.click();
32640     },
32641
32642     // private
32643     handleMouseUp : function(e){
32644         clearTimeout(this.timer);
32645         this.el.un("mouseover", this.handleMouseReturn, this);
32646         this.el.un("mouseout", this.handleMouseOut, this);
32647         Ext.getDoc().un("mouseup", this.handleMouseUp, this);
32648         if(this.pressedCls){
32649             this.el.removeCls(this.pressedCls);
32650         }
32651         this.fireEvent("mouseup", this, e);
32652     }
32653 });
32654
32655 /**
32656  * Component layout for buttons
32657  * @class Ext.layout.component.Button
32658  * @extends Ext.layout.component.Component
32659  * @private
32660  */
32661 Ext.define('Ext.layout.component.Button', {
32662
32663     /* Begin Definitions */
32664
32665     alias: ['layout.button'],
32666
32667     extend: 'Ext.layout.component.Component',
32668
32669     /* End Definitions */
32670
32671     type: 'button',
32672
32673     cellClsRE: /-btn-(tl|br)\b/,
32674     htmlRE: /<.*>/,
32675
32676     beforeLayout: function() {
32677         return this.callParent(arguments) || this.lastText !== this.owner.text;
32678     },
32679
32680     /**
32681      * Set the dimensions of the inner &lt;button&gt; element to match the
32682      * component dimensions.
32683      */
32684     onLayout: function(width, height) {
32685         var me = this,
32686             isNum = Ext.isNumber,
32687             owner = me.owner,
32688             ownerEl = owner.el,
32689             btnEl = owner.btnEl,
32690             btnInnerEl = owner.btnInnerEl,
32691             minWidth = owner.minWidth,
32692             maxWidth = owner.maxWidth,
32693             ownerWidth, btnFrameWidth, metrics;
32694
32695         me.getTargetInfo();
32696         me.callParent(arguments);
32697
32698         btnInnerEl.unclip();
32699         me.setTargetSize(width, height);
32700
32701         if (!isNum(width)) {
32702             // In IE7 strict mode button elements with width:auto get strange extra side margins within
32703             // the wrapping table cell, but they go away if the width is explicitly set. So we measure
32704             // the size of the text and set the width to match.
32705             if (owner.text && Ext.isIE7 && Ext.isStrict && btnEl && btnEl.getWidth() > 20) {
32706                 btnFrameWidth = me.btnFrameWidth;
32707                 metrics = Ext.util.TextMetrics.measure(btnInnerEl, owner.text);
32708                 ownerEl.setWidth(metrics.width + btnFrameWidth + me.adjWidth);
32709                 btnEl.setWidth(metrics.width + btnFrameWidth);
32710                 btnInnerEl.setWidth(metrics.width + btnFrameWidth);
32711             } else {
32712                 // Remove any previous fixed widths
32713                 ownerEl.setWidth(null);
32714                 btnEl.setWidth(null);
32715                 btnInnerEl.setWidth(null);
32716             }
32717
32718             // Handle maxWidth/minWidth config
32719             if (minWidth || maxWidth) {
32720                 ownerWidth = ownerEl.getWidth();
32721                 if (minWidth && (ownerWidth < minWidth)) {
32722                     me.setTargetSize(minWidth, height);
32723                 }
32724                 else if (maxWidth && (ownerWidth > maxWidth)) {
32725                     btnInnerEl.clip();
32726                     me.setTargetSize(maxWidth, height);
32727                 }
32728             }
32729         }
32730
32731         this.lastText = owner.text;
32732     },
32733
32734     setTargetSize: function(width, height) {
32735         var me = this,
32736             owner = me.owner,
32737             isNum = Ext.isNumber,
32738             btnInnerEl = owner.btnInnerEl,
32739             btnWidth = (isNum(width) ? width - me.adjWidth : width),
32740             btnHeight = (isNum(height) ? height - me.adjHeight : height),
32741             btnFrameHeight = me.btnFrameHeight,
32742             text = owner.getText(),
32743             textHeight;
32744
32745         me.callParent(arguments);
32746         me.setElementSize(owner.btnEl, btnWidth, btnHeight);
32747         me.setElementSize(btnInnerEl, btnWidth, btnHeight);
32748         if (isNum(btnHeight)) {
32749             btnInnerEl.setStyle('line-height', btnHeight - btnFrameHeight + 'px');
32750         }
32751
32752         // Button text may contain markup that would force it to wrap to more than one line (e.g. 'Button<br>Label').
32753         // When this happens, we cannot use the line-height set above for vertical centering; we instead reset the
32754         // line-height to normal, measure the rendered text height, and add padding-top to center the text block
32755         // vertically within the button's height. This is more expensive than the basic line-height approach so
32756         // we only do it if the text contains markup.
32757         if (text && this.htmlRE.test(text)) {
32758             btnInnerEl.setStyle('line-height', 'normal');
32759             textHeight = Ext.util.TextMetrics.measure(btnInnerEl, text).height;
32760             btnInnerEl.setStyle('padding-top', me.btnFrameTop + Math.max(btnInnerEl.getHeight() - btnFrameHeight - textHeight, 0) / 2 + 'px');
32761             me.setElementSize(btnInnerEl, btnWidth, btnHeight);
32762         }
32763     },
32764
32765     getTargetInfo: function() {
32766         var me = this,
32767             owner = me.owner,
32768             ownerEl = owner.el,
32769             frameSize = me.frameSize,
32770             frameBody = owner.frameBody,
32771             btnWrap = owner.btnWrap,
32772             innerEl = owner.btnInnerEl;
32773
32774         if (!('adjWidth' in me)) {
32775             Ext.apply(me, {
32776                 // Width adjustment must take into account the arrow area. The btnWrap is the <em> which has padding to accommodate the arrow.
32777                 adjWidth: frameSize.left + frameSize.right + ownerEl.getBorderWidth('lr') + ownerEl.getPadding('lr') +
32778                           btnWrap.getPadding('lr') + (frameBody ? frameBody.getFrameWidth('lr') : 0),
32779                 adjHeight: frameSize.top + frameSize.bottom + ownerEl.getBorderWidth('tb') + ownerEl.getPadding('tb') +
32780                            btnWrap.getPadding('tb') + (frameBody ? frameBody.getFrameWidth('tb') : 0),
32781                 btnFrameWidth: innerEl.getFrameWidth('lr'),
32782                 btnFrameHeight: innerEl.getFrameWidth('tb'),
32783                 btnFrameTop: innerEl.getFrameWidth('t')
32784             });
32785         }
32786
32787         return me.callParent();
32788     }
32789 });
32790 /**
32791  * @class Ext.util.TextMetrics
32792  * <p>
32793  * Provides precise pixel measurements for blocks of text so that you can determine exactly how high and
32794  * wide, in pixels, a given block of text will be. Note that when measuring text, it should be plain text and
32795  * should not contain any HTML, otherwise it may not be measured correctly.</p> 
32796  * <p>The measurement works by copying the relevant CSS styles that can affect the font related display, 
32797  * then checking the size of an element that is auto-sized. Note that if the text is multi-lined, you must 
32798  * provide a <b>fixed width</b> when doing the measurement.</p>
32799  * 
32800  * <p>
32801  * If multiple measurements are being done on the same element, you create a new instance to initialize 
32802  * to avoid the overhead of copying the styles to the element repeatedly.
32803  * </p>
32804  */
32805 Ext.define('Ext.util.TextMetrics', {
32806     statics: {
32807         shared: null,
32808         /**
32809          * Measures the size of the specified text
32810          * @param {String/HTMLElement} el The element, dom node or id from which to copy existing CSS styles
32811          * that can affect the size of the rendered text
32812          * @param {String} text The text to measure
32813          * @param {Number} fixedWidth (optional) If the text will be multiline, you have to set a fixed width
32814          * in order to accurately measure the text height
32815          * @return {Object} An object containing the text's size {width: (width), height: (height)}
32816          */
32817         measure: function(el, text, fixedWidth){
32818             var me = this,
32819                 shared = me.shared;
32820             
32821             if(!shared){
32822                 shared = me.shared = new me(el, fixedWidth);
32823             }
32824             shared.bind(el);
32825             shared.setFixedWidth(fixedWidth || 'auto');
32826             return shared.getSize(text);
32827         },
32828         
32829         /**
32830           * Destroy the TextMetrics instance created by {@link #measure}.
32831           */
32832          destroy: function(){
32833              var me = this;
32834              Ext.destroy(me.shared);
32835              me.shared = null;
32836          }
32837     },
32838     
32839     /**
32840      * @constructor
32841      * @param {Mixed} bindTo The element to bind to.
32842      * @param {Number} fixedWidth A fixed width to apply to the measuring element.
32843      */
32844     constructor: function(bindTo, fixedWidth){
32845         var measure = this.measure = Ext.getBody().createChild({
32846             cls: 'x-textmetrics'
32847         });
32848         this.el = Ext.get(bindTo);
32849         
32850         measure.position('absolute');
32851         measure.setLeftTop(-1000, -1000);
32852         measure.hide();
32853
32854         if (fixedWidth) {
32855            measure.setWidth(fixedWidth);
32856         }
32857     },
32858     
32859     /**
32860      * <p><b>Only available on the instance returned from {@link #createInstance}, <u>not</u> on the singleton.</b></p>
32861      * Returns the size of the specified text based on the internal element's style and width properties
32862      * @param {String} text The text to measure
32863      * @return {Object} An object containing the text's size {width: (width), height: (height)}
32864      */
32865     getSize: function(text){
32866         var measure = this.measure,
32867             size;
32868         
32869         measure.update(text);
32870         size = measure.getSize();
32871         measure.update('');
32872         return size;
32873     },
32874     
32875     /**
32876      * Binds this TextMetrics instance to a new element
32877      * @param {Mixed} el The element
32878      */
32879     bind: function(el){
32880         var me = this;
32881         
32882         me.el = Ext.get(el);
32883         me.measure.setStyle(
32884             me.el.getStyles('font-size','font-style', 'font-weight', 'font-family','line-height', 'text-transform', 'letter-spacing')
32885         );
32886     },
32887     
32888     /**
32889      * Sets a fixed width on the internal measurement element.  If the text will be multiline, you have
32890      * to set a fixed width in order to accurately measure the text height.
32891      * @param {Number} width The width to set on the element
32892      */
32893      setFixedWidth : function(width){
32894          this.measure.setWidth(width);
32895      },
32896      
32897      /**
32898       * Returns the measured width of the specified text
32899       * @param {String} text The text to measure
32900       * @return {Number} width The width in pixels
32901       */
32902      getWidth : function(text){
32903          this.measure.dom.style.width = 'auto';
32904          return this.getSize(text).width;
32905      },
32906      
32907      /**
32908       * Returns the measured height of the specified text
32909       * @param {String} text The text to measure
32910       * @return {Number} height The height in pixels
32911       */
32912      getHeight : function(text){
32913          return this.getSize(text).height;
32914      },
32915      
32916      /**
32917       * Destroy this instance
32918       */
32919      destroy: function(){
32920          var me = this;
32921          me.measure.remove();
32922          delete me.el;
32923          delete me.measure;
32924      }
32925 }, function(){
32926     Ext.core.Element.addMethods({
32927         /**
32928          * Returns the width in pixels of the passed text, or the width of the text in this Element.
32929          * @param {String} text The text to measure. Defaults to the innerHTML of the element.
32930          * @param {Number} min (Optional) The minumum value to return.
32931          * @param {Number} max (Optional) The maximum value to return.
32932          * @return {Number} The text width in pixels.
32933          * @member Ext.core.Element getTextWidth
32934          */
32935         getTextWidth : function(text, min, max){
32936             return Ext.Number.constrain(Ext.util.TextMetrics.measure(this.dom, Ext.value(text, this.dom.innerHTML, true)).width, min || 0, max || 1000000);
32937         }
32938     });
32939 });
32940
32941 /**
32942  * @class Ext.layout.container.boxOverflow.Scroller
32943  * @extends Ext.layout.container.boxOverflow.None
32944  * @private
32945  */
32946 Ext.define('Ext.layout.container.boxOverflow.Scroller', {
32947
32948     /* Begin Definitions */
32949
32950     extend: 'Ext.layout.container.boxOverflow.None',
32951     requires: ['Ext.util.ClickRepeater', 'Ext.core.Element'],
32952     alternateClassName: 'Ext.layout.boxOverflow.Scroller',
32953     mixins: {
32954         observable: 'Ext.util.Observable'
32955     },
32956     
32957     /* End Definitions */
32958
32959     /**
32960      * @cfg {Boolean} animateScroll
32961      * True to animate the scrolling of items within the layout (defaults to true, ignored if enableScroll is false)
32962      */
32963     animateScroll: false,
32964
32965     /**
32966      * @cfg {Number} scrollIncrement
32967      * The number of pixels to scroll by on scroller click (defaults to 24)
32968      */
32969     scrollIncrement: 20,
32970
32971     /**
32972      * @cfg {Number} wheelIncrement
32973      * The number of pixels to increment on mouse wheel scrolling (defaults to <tt>3</tt>).
32974      */
32975     wheelIncrement: 10,
32976
32977     /**
32978      * @cfg {Number} scrollRepeatInterval
32979      * Number of milliseconds between each scroll while a scroller button is held down (defaults to 20)
32980      */
32981     scrollRepeatInterval: 60,
32982
32983     /**
32984      * @cfg {Number} scrollDuration
32985      * Number of milliseconds that each scroll animation lasts (defaults to 400)
32986      */
32987     scrollDuration: 400,
32988
32989     /**
32990      * @cfg {String} beforeCtCls
32991      * CSS class added to the beforeCt element. This is the element that holds any special items such as scrollers,
32992      * which must always be present at the leftmost edge of the Container
32993      */
32994
32995     /**
32996      * @cfg {String} afterCtCls
32997      * CSS class added to the afterCt element. This is the element that holds any special items such as scrollers,
32998      * which must always be present at the rightmost edge of the Container
32999      */
33000
33001     /**
33002      * @cfg {String} scrollerCls
33003      * CSS class added to both scroller elements if enableScroll is used
33004      */
33005     scrollerCls: Ext.baseCSSPrefix + 'box-scroller',
33006
33007     /**
33008      * @cfg {String} beforeScrollerCls
33009      * CSS class added to the left scroller element if enableScroll is used
33010      */
33011
33012     /**
33013      * @cfg {String} afterScrollerCls
33014      * CSS class added to the right scroller element if enableScroll is used
33015      */
33016     
33017     constructor: function(layout, config) {
33018         this.layout = layout;
33019         Ext.apply(this, config || {});
33020         
33021         this.addEvents(
33022             /**
33023              * @event scroll
33024              * @param {Ext.layout.container.boxOverflow.Scroller} scroller The layout scroller
33025              * @param {Number} newPosition The new position of the scroller
33026              * @param {Boolean/Object} animate If animating or not. If true, it will be a animation configuration, else it will be false
33027              */
33028             'scroll'
33029         );
33030     },
33031     
33032     initCSSClasses: function() {
33033         var me = this,
33034         layout = me.layout;
33035
33036         if (!me.CSSinitialized) {
33037             me.beforeCtCls = me.beforeCtCls || Ext.baseCSSPrefix + 'box-scroller-' + layout.parallelBefore;
33038             me.afterCtCls  = me.afterCtCls  || Ext.baseCSSPrefix + 'box-scroller-' + layout.parallelAfter;
33039             me.beforeScrollerCls = me.beforeScrollerCls || Ext.baseCSSPrefix + layout.owner.getXType() + '-scroll-' + layout.parallelBefore;
33040             me.afterScrollerCls  = me.afterScrollerCls  || Ext.baseCSSPrefix + layout.owner.getXType() + '-scroll-' + layout.parallelAfter;
33041             me.CSSinitializes = true;
33042         }
33043     },
33044
33045     handleOverflow: function(calculations, targetSize) {
33046         var me = this,
33047             layout = me.layout,
33048             methodName = 'get' + layout.parallelPrefixCap,
33049             newSize = {};
33050
33051         me.initCSSClasses();
33052         me.callParent(arguments);
33053         this.createInnerElements();
33054         this.showScrollers();
33055         newSize[layout.perpendicularPrefix] = targetSize[layout.perpendicularPrefix];
33056         newSize[layout.parallelPrefix] = targetSize[layout.parallelPrefix] - (me.beforeCt[methodName]() + me.afterCt[methodName]());
33057         return { targetSize: newSize };
33058     },
33059
33060     /**
33061      * @private
33062      * Creates the beforeCt and afterCt elements if they have not already been created
33063      */
33064     createInnerElements: function() {
33065         var me = this,
33066             target = me.layout.getRenderTarget();
33067
33068         //normal items will be rendered to the innerCt. beforeCt and afterCt allow for fixed positioning of
33069         //special items such as scrollers or dropdown menu triggers
33070         if (!me.beforeCt) {
33071             target.addCls(Ext.baseCSSPrefix + me.layout.direction + '-box-overflow-body');
33072             me.beforeCt = target.insertSibling({cls: Ext.layout.container.Box.prototype.innerCls + ' ' + me.beforeCtCls}, 'before');
33073             me.afterCt  = target.insertSibling({cls: Ext.layout.container.Box.prototype.innerCls + ' ' + me.afterCtCls},  'after');
33074             me.createWheelListener();
33075         }
33076     },
33077
33078     /**
33079      * @private
33080      * Sets up an listener to scroll on the layout's innerCt mousewheel event
33081      */
33082     createWheelListener: function() {
33083         this.layout.innerCt.on({
33084             scope     : this,
33085             mousewheel: function(e) {
33086                 e.stopEvent();
33087
33088                 this.scrollBy(e.getWheelDelta() * this.wheelIncrement * -1, false);
33089             }
33090         });
33091     },
33092
33093     /**
33094      * @private
33095      */
33096     clearOverflow: function() {
33097         this.hideScrollers();
33098     },
33099
33100     /**
33101      * @private
33102      * Shows the scroller elements in the beforeCt and afterCt. Creates the scrollers first if they are not already
33103      * present. 
33104      */
33105     showScrollers: function() {
33106         this.createScrollers();
33107         this.beforeScroller.show();
33108         this.afterScroller.show();
33109         this.updateScrollButtons();
33110         
33111         this.layout.owner.addClsWithUI('scroller');
33112     },
33113
33114     /**
33115      * @private
33116      * Hides the scroller elements in the beforeCt and afterCt
33117      */
33118     hideScrollers: function() {
33119         if (this.beforeScroller != undefined) {
33120             this.beforeScroller.hide();
33121             this.afterScroller.hide();
33122             
33123             this.layout.owner.removeClsWithUI('scroller');
33124         }
33125     },
33126
33127     /**
33128      * @private
33129      * Creates the clickable scroller elements and places them into the beforeCt and afterCt
33130      */
33131     createScrollers: function() {
33132         if (!this.beforeScroller && !this.afterScroller) {
33133             var before = this.beforeCt.createChild({
33134                 cls: Ext.String.format("{0} {1} ", this.scrollerCls, this.beforeScrollerCls)
33135             });
33136
33137             var after = this.afterCt.createChild({
33138                 cls: Ext.String.format("{0} {1}", this.scrollerCls, this.afterScrollerCls)
33139             });
33140
33141             before.addClsOnOver(this.beforeScrollerCls + '-hover');
33142             after.addClsOnOver(this.afterScrollerCls + '-hover');
33143
33144             before.setVisibilityMode(Ext.core.Element.DISPLAY);
33145             after.setVisibilityMode(Ext.core.Element.DISPLAY);
33146
33147             this.beforeRepeater = Ext.create('Ext.util.ClickRepeater', before, {
33148                 interval: this.scrollRepeatInterval,
33149                 handler : this.scrollLeft,
33150                 scope   : this
33151             });
33152
33153             this.afterRepeater = Ext.create('Ext.util.ClickRepeater', after, {
33154                 interval: this.scrollRepeatInterval,
33155                 handler : this.scrollRight,
33156                 scope   : this
33157             });
33158
33159             /**
33160              * @property beforeScroller
33161              * @type Ext.core.Element
33162              * The left scroller element. Only created when needed.
33163              */
33164             this.beforeScroller = before;
33165
33166             /**
33167              * @property afterScroller
33168              * @type Ext.core.Element
33169              * The left scroller element. Only created when needed.
33170              */
33171             this.afterScroller = after;
33172         }
33173     },
33174
33175     /**
33176      * @private
33177      */
33178     destroy: function() {
33179         Ext.destroy(this.beforeRepeater, this.afterRepeater, this.beforeScroller, this.afterScroller, this.beforeCt, this.afterCt);
33180     },
33181
33182     /**
33183      * @private
33184      * Scrolls left or right by the number of pixels specified
33185      * @param {Number} delta Number of pixels to scroll to the right by. Use a negative number to scroll left
33186      */
33187     scrollBy: function(delta, animate) {
33188         this.scrollTo(this.getScrollPosition() + delta, animate);
33189     },
33190
33191     /**
33192      * @private
33193      * @return {Object} Object passed to scrollTo when scrolling
33194      */
33195     getScrollAnim: function() {
33196         return {
33197             duration: this.scrollDuration, 
33198             callback: this.updateScrollButtons, 
33199             scope   : this
33200         };
33201     },
33202
33203     /**
33204      * @private
33205      * Enables or disables each scroller button based on the current scroll position
33206      */
33207     updateScrollButtons: function() {
33208         if (this.beforeScroller == undefined || this.afterScroller == undefined) {
33209             return;
33210         }
33211
33212         var beforeMeth = this.atExtremeBefore()  ? 'addCls' : 'removeCls',
33213             afterMeth  = this.atExtremeAfter() ? 'addCls' : 'removeCls',
33214             beforeCls  = this.beforeScrollerCls + '-disabled',
33215             afterCls   = this.afterScrollerCls  + '-disabled';
33216         
33217         this.beforeScroller[beforeMeth](beforeCls);
33218         this.afterScroller[afterMeth](afterCls);
33219         this.scrolling = false;
33220     },
33221
33222     /**
33223      * @private
33224      * Returns true if the innerCt scroll is already at its left-most point
33225      * @return {Boolean} True if already at furthest left point
33226      */
33227     atExtremeBefore: function() {
33228         return this.getScrollPosition() === 0;
33229     },
33230
33231     /**
33232      * @private
33233      * Scrolls to the left by the configured amount
33234      */
33235     scrollLeft: function() {
33236         this.scrollBy(-this.scrollIncrement, false);
33237     },
33238
33239     /**
33240      * @private
33241      * Scrolls to the right by the configured amount
33242      */
33243     scrollRight: function() {
33244         this.scrollBy(this.scrollIncrement, false);
33245     },
33246
33247     /**
33248      * Returns the current scroll position of the innerCt element
33249      * @return {Number} The current scroll position
33250      */
33251     getScrollPosition: function(){
33252         var layout = this.layout;
33253         return parseInt(layout.innerCt.dom['scroll' + layout.parallelBeforeCap], 10) || 0;
33254     },
33255
33256     /**
33257      * @private
33258      * Returns the maximum value we can scrollTo
33259      * @return {Number} The max scroll value
33260      */
33261     getMaxScrollPosition: function() {
33262         var layout = this.layout;
33263         return layout.innerCt.dom['scroll' + layout.parallelPrefixCap] - this.layout.innerCt['get' + layout.parallelPrefixCap]();
33264     },
33265
33266     /**
33267      * @private
33268      * Returns true if the innerCt scroll is already at its right-most point
33269      * @return {Boolean} True if already at furthest right point
33270      */
33271     atExtremeAfter: function() {
33272         return this.getScrollPosition() >= this.getMaxScrollPosition();
33273     },
33274
33275     /**
33276      * @private
33277      * Scrolls to the given position. Performs bounds checking.
33278      * @param {Number} position The position to scroll to. This is constrained.
33279      * @param {Boolean} animate True to animate. If undefined, falls back to value of this.animateScroll
33280      */
33281     scrollTo: function(position, animate) {
33282         var me = this,
33283             layout = me.layout,
33284             oldPosition = me.getScrollPosition(),
33285             newPosition = Ext.Number.constrain(position, 0, me.getMaxScrollPosition());
33286
33287         if (newPosition != oldPosition && !me.scrolling) {
33288             if (animate == undefined) {
33289                 animate = me.animateScroll;
33290             }
33291
33292             layout.innerCt.scrollTo(layout.parallelBefore, newPosition, animate ? me.getScrollAnim() : false);
33293             if (animate) {
33294                 me.scrolling = true;
33295             } else {
33296                 me.scrolling = false;
33297                 me.updateScrollButtons();
33298             }
33299             
33300             me.fireEvent('scroll', me, newPosition, animate ? me.getScrollAnim() : false);
33301         }
33302     },
33303
33304     /**
33305      * Scrolls to the given component.
33306      * @param {String|Number|Ext.Component} item The item to scroll to. Can be a numerical index, component id 
33307      * or a reference to the component itself.
33308      * @param {Boolean} animate True to animate the scrolling
33309      */
33310     scrollToItem: function(item, animate) {
33311         var me = this,
33312             layout = me.layout,
33313             visibility,
33314             box,
33315             newPos;
33316
33317         item = me.getItem(item);
33318         if (item != undefined) {
33319             visibility = this.getItemVisibility(item);
33320             if (!visibility.fullyVisible) {
33321                 box  = item.getBox(true, true);
33322                 newPos = box[layout.parallelPosition];
33323                 if (visibility.hiddenEnd) {
33324                     newPos -= (this.layout.innerCt['get' + layout.parallelPrefixCap]() - box[layout.parallelPrefix]);
33325                 }
33326                 this.scrollTo(newPos, animate);
33327             }
33328         }
33329     },
33330
33331     /**
33332      * @private
33333      * For a given item in the container, return an object with information on whether the item is visible
33334      * with the current innerCt scroll value.
33335      * @param {Ext.Component} item The item
33336      * @return {Object} Values for fullyVisible, hiddenStart and hiddenEnd
33337      */
33338     getItemVisibility: function(item) {
33339         var me          = this,
33340             box         = me.getItem(item).getBox(true, true),
33341             layout      = me.layout,
33342             itemStart   = box[layout.parallelPosition],
33343             itemEnd     = itemStart + box[layout.parallelPrefix],
33344             scrollStart = me.getScrollPosition(),
33345             scrollEnd   = scrollStart + layout.innerCt['get' + layout.parallelPrefixCap]();
33346
33347         return {
33348             hiddenStart : itemStart < scrollStart,
33349             hiddenEnd   : itemEnd > scrollEnd,
33350             fullyVisible: itemStart > scrollStart && itemEnd < scrollEnd
33351         };
33352     }
33353 });
33354 /**
33355  * @class Ext.util.Offset
33356  * @ignore
33357  */
33358 Ext.define('Ext.util.Offset', {
33359
33360     /* Begin Definitions */
33361
33362     statics: {
33363         fromObject: function(obj) {
33364             return new this(obj.x, obj.y);
33365         }
33366     },
33367
33368     /* End Definitions */
33369
33370     constructor: function(x, y) {
33371         this.x = (x != null && !isNaN(x)) ? x : 0;
33372         this.y = (y != null && !isNaN(y)) ? y : 0;
33373
33374         return this;
33375     },
33376
33377     copy: function() {
33378         return new Ext.util.Offset(this.x, this.y);
33379     },
33380
33381     copyFrom: function(p) {
33382         this.x = p.x;
33383         this.y = p.y;
33384     },
33385
33386     toString: function() {
33387         return "Offset[" + this.x + "," + this.y + "]";
33388     },
33389
33390     equals: function(offset) {
33391         if(!(offset instanceof this.statics())) {
33392             Ext.Error.raise('Offset must be an instance of Ext.util.Offset');
33393         }
33394
33395         return (this.x == offset.x && this.y == offset.y);
33396     },
33397
33398     round: function(to) {
33399         if (!isNaN(to)) {
33400             var factor = Math.pow(10, to);
33401             this.x = Math.round(this.x * factor) / factor;
33402             this.y = Math.round(this.y * factor) / factor;
33403         } else {
33404             this.x = Math.round(this.x);
33405             this.y = Math.round(this.y);
33406         }
33407     },
33408
33409     isZero: function() {
33410         return this.x == 0 && this.y == 0;
33411     }
33412 });
33413
33414 /**
33415  * @class Ext.util.KeyNav
33416  * <p>Provides a convenient wrapper for normalized keyboard navigation.  KeyNav allows you to bind
33417  * navigation keys to function calls that will get called when the keys are pressed, providing an easy
33418  * way to implement custom navigation schemes for any UI component.</p>
33419  * <p>The following are all of the possible keys that can be implemented: enter, space, left, right, up, down, tab, esc,
33420  * pageUp, pageDown, del, backspace, home, end.  Usage:</p>
33421  <pre><code>
33422 var nav = new Ext.util.KeyNav("my-element", {
33423     "left" : function(e){
33424         this.moveLeft(e.ctrlKey);
33425     },
33426     "right" : function(e){
33427         this.moveRight(e.ctrlKey);
33428     },
33429     "enter" : function(e){
33430         this.save();
33431     },
33432     scope : this
33433 });
33434 </code></pre>
33435  * @constructor
33436  * @param {Mixed} el The element to bind to
33437  * @param {Object} config The config
33438  */
33439 Ext.define('Ext.util.KeyNav', {
33440     
33441     alternateClassName: 'Ext.KeyNav',
33442     
33443     requires: ['Ext.util.KeyMap'],
33444     
33445     statics: {
33446         keyOptions: {
33447             left: 37,
33448             right: 39,
33449             up: 38,
33450             down: 40,
33451             space: 32,
33452             pageUp: 33,
33453             pageDown: 34,
33454             del: 46,
33455             backspace: 8,
33456             home: 36,
33457             end: 35,
33458             enter: 13,
33459             esc: 27,
33460             tab: 9
33461         }
33462     },
33463     
33464     constructor: function(el, config){
33465         this.setConfig(el, config || {});
33466     },
33467     
33468     /**
33469      * Sets up a configuration for the KeyNav.
33470      * @private
33471      * @param {Mixed} el The element to bind to
33472      * @param {Object}A configuration object as specified in the constructor.
33473      */
33474     setConfig: function(el, config) {
33475         if (this.map) {
33476             this.map.destroy();
33477         }
33478         
33479         var map = Ext.create('Ext.util.KeyMap', el, null, this.getKeyEvent('forceKeyDown' in config ? config.forceKeyDown : this.forceKeyDown)),
33480             keys = Ext.util.KeyNav.keyOptions,
33481             scope = config.scope || this,
33482             key;
33483         
33484         this.map = map;
33485         for (key in keys) {
33486             if (keys.hasOwnProperty(key)) {
33487                 if (config[key]) {
33488                     map.addBinding({
33489                         scope: scope,
33490                         key: keys[key],
33491                         handler: Ext.Function.bind(this.handleEvent, scope, [config[key]], true),
33492                         defaultEventAction: config.defaultEventAction || this.defaultEventAction
33493                     });
33494                 }
33495             }
33496         }
33497         
33498         map.disable();
33499         if (!config.disabled) {
33500             map.enable();
33501         }
33502     },
33503     
33504     /**
33505      * Method for filtering out the map argument
33506      * @private
33507      * @param {Ext.util.KeyMap} map
33508      * @param {Ext.EventObject} event
33509      * @param {Object} options Contains the handler to call
33510      */
33511     handleEvent: function(map, event, handler){
33512         return handler.call(this, event);
33513     },
33514     
33515     /**
33516      * @cfg {Boolean} disabled
33517      * True to disable this KeyNav instance (defaults to false)
33518      */
33519     disabled: false,
33520     
33521     /**
33522      * @cfg {String} defaultEventAction
33523      * The method to call on the {@link Ext.EventObject} after this KeyNav intercepts a key.  Valid values are
33524      * {@link Ext.EventObject#stopEvent}, {@link Ext.EventObject#preventDefault} and
33525      * {@link Ext.EventObject#stopPropagation} (defaults to 'stopEvent')
33526      */
33527     defaultEventAction: "stopEvent",
33528     
33529     /**
33530      * @cfg {Boolean} forceKeyDown
33531      * Handle the keydown event instead of keypress (defaults to false).  KeyNav automatically does this for IE since
33532      * IE does not propagate special keys on keypress, but setting this to true will force other browsers to also
33533      * handle keydown instead of keypress.
33534      */
33535     forceKeyDown: false,
33536     
33537     /**
33538      * Destroy this KeyNav (this is the same as calling disable).
33539      * @param {Boolean} removeEl True to remove the element associated with this KeyNav.
33540      */
33541     destroy: function(removeEl){
33542         this.map.destroy(removeEl);
33543         delete this.map;
33544     },
33545
33546     /**
33547      * Enable this KeyNav
33548      */
33549     enable: function() {
33550         this.map.enable();
33551         this.disabled = false;
33552     },
33553
33554     /**
33555      * Disable this KeyNav
33556      */
33557     disable: function() {
33558         this.map.disable();
33559         this.disabled = true;
33560     },
33561     
33562     /**
33563      * Convenience function for setting disabled/enabled by boolean.
33564      * @param {Boolean} disabled
33565      */
33566     setDisabled : function(disabled){
33567         this.map.setDisabled(disabled);
33568         this.disabled = disabled;
33569     },
33570     
33571     /**
33572      * Determines the event to bind to listen for keys. Depends on the {@link #forceKeyDown} setting,
33573      * as well as the useKeyDown option on the EventManager.
33574      * @return {String} The type of event to listen for.
33575      */
33576     getKeyEvent: function(forceKeyDown){
33577         return (forceKeyDown || Ext.EventManager.useKeyDown) ? 'keydown' : 'keypress';
33578     }
33579 });
33580
33581 /**
33582  * @class Ext.fx.Queue
33583  * Animation Queue mixin to handle chaining and queueing by target.
33584  * @private
33585  */
33586
33587 Ext.define('Ext.fx.Queue', {
33588
33589     requires: ['Ext.util.HashMap'],
33590
33591     constructor: function() {
33592         this.targets = Ext.create('Ext.util.HashMap');
33593         this.fxQueue = {};
33594     },
33595
33596     // @private
33597     getFxDefaults: function(targetId) {
33598         var target = this.targets.get(targetId);
33599         if (target) {
33600             return target.fxDefaults;
33601         }
33602         return {};
33603     },
33604
33605     // @private
33606     setFxDefaults: function(targetId, obj) {
33607         var target = this.targets.get(targetId);
33608         if (target) {
33609             target.fxDefaults = Ext.apply(target.fxDefaults || {}, obj);
33610         }
33611     },
33612
33613     // @private
33614     stopAnimation: function(targetId) {
33615         var me = this,
33616             queue = me.getFxQueue(targetId),
33617             ln = queue.length;
33618         while (ln) {
33619             queue[ln - 1].end();
33620             ln--;
33621         }
33622     },
33623
33624     /**
33625      * @private
33626      * Returns current animation object if the element has any effects actively running or queued, else returns false.
33627      */
33628     getActiveAnimation: function(targetId) {
33629         var queue = this.getFxQueue(targetId);
33630         return (queue && !!queue.length) ? queue[0] : false;
33631     },
33632
33633     // @private
33634     hasFxBlock: function(targetId) {
33635         var queue = this.getFxQueue(targetId);
33636         return queue && queue[0] && queue[0].block;
33637     },
33638
33639     // @private get fx queue for passed target, create if needed.
33640     getFxQueue: function(targetId) {
33641         if (!targetId) {
33642             return false;
33643         }
33644         var me = this,
33645             queue = me.fxQueue[targetId],
33646             target = me.targets.get(targetId);
33647
33648         if (!target) {
33649             return false;
33650         }
33651
33652         if (!queue) {
33653             me.fxQueue[targetId] = [];
33654             // GarbageCollector will need to clean up Elements since they aren't currently observable
33655             if (target.type != 'element') {
33656                 target.target.on('destroy', function() {
33657                     me.fxQueue[targetId] = [];
33658                 });
33659             }
33660         }
33661         return me.fxQueue[targetId];
33662     },
33663
33664     // @private
33665     queueFx: function(anim) {
33666         var me = this,
33667             target = anim.target,
33668             queue, ln;
33669
33670         if (!target) {
33671             return;
33672         }
33673
33674         queue = me.getFxQueue(target.getId());
33675         ln = queue.length;
33676
33677         if (ln) {
33678             if (anim.concurrent) {
33679                 anim.paused = false;
33680             }
33681             else {
33682                 queue[ln - 1].on('afteranimate', function() {
33683                     anim.paused = false;
33684                 });
33685             }
33686         }
33687         else {
33688             anim.paused = false;
33689         }
33690         anim.on('afteranimate', function() {
33691             Ext.Array.remove(queue, anim);
33692             if (anim.remove) {
33693                 if (target.type == 'element') {
33694                     var el = Ext.get(target.id);
33695                     if (el) {
33696                         el.remove();
33697                     }
33698                 }
33699             }
33700         }, this);
33701         queue.push(anim);
33702     }
33703 });
33704 /**
33705  * @class Ext.fx.target.Target
33706
33707 This class specifies a generic target for an animation. It provides a wrapper around a
33708 series of different types of objects to allow for a generic animation API.
33709 A target can be a single object or a Composite object containing other objects that are 
33710 to be animated. This class and it's subclasses are generally not created directly, the 
33711 underlying animation will create the appropriate Ext.fx.target.Target object by passing 
33712 the instance to be animated.
33713
33714 The following types of objects can be animated:
33715 - {@link #Ext.fx.target.Component Components}
33716 - {@link #Ext.fx.target.Element Elements}
33717 - {@link #Ext.fx.target.Sprite Sprites}
33718
33719  * @markdown
33720  * @abstract
33721  * @constructor
33722  * @param {Mixed} target The object to be animated
33723  */
33724
33725 Ext.define('Ext.fx.target.Target', {
33726
33727     isAnimTarget: true,
33728
33729     constructor: function(target) {
33730         this.target = target;
33731         this.id = this.getId();
33732     },
33733     
33734     getId: function() {
33735         return this.target.id;
33736     }
33737 });
33738
33739 /**
33740  * @class Ext.fx.target.Sprite
33741  * @extends Ext.fx.target.Target
33742
33743 This class represents a animation target for a {@link Ext.draw.Sprite}. In general this class will not be
33744 created directly, the {@link Ext.draw.Sprite} will be passed to the animation and
33745 and the appropriate target will be created.
33746
33747  * @markdown
33748  */
33749
33750 Ext.define('Ext.fx.target.Sprite', {
33751
33752     /* Begin Definitions */
33753
33754     extend: 'Ext.fx.target.Target',
33755
33756     /* End Definitions */
33757
33758     type: 'draw',
33759
33760     getFromPrim: function(sprite, attr) {
33761         var o;
33762         if (attr == 'translate') {
33763             o = {
33764                 x: sprite.attr.translation.x || 0,
33765                 y: sprite.attr.translation.y || 0
33766             };
33767         }
33768         else if (attr == 'rotate') {
33769             o = {
33770                 degrees: sprite.attr.rotation.degrees || 0,
33771                 x: sprite.attr.rotation.x,
33772                 y: sprite.attr.rotation.y
33773             };
33774         }
33775         else {
33776             o = sprite.attr[attr];
33777         }
33778         return o;
33779     },
33780
33781     getAttr: function(attr, val) {
33782         return [[this.target, val != undefined ? val : this.getFromPrim(this.target, attr)]];
33783     },
33784
33785     setAttr: function(targetData) {
33786         var ln = targetData.length,
33787             spriteArr = [],
33788             attrs, attr, attrArr, attPtr, spritePtr, idx, value, i, j, x, y, ln2;
33789         for (i = 0; i < ln; i++) {
33790             attrs = targetData[i].attrs;
33791             for (attr in attrs) {
33792                 attrArr = attrs[attr];
33793                 ln2 = attrArr.length;
33794                 for (j = 0; j < ln2; j++) {
33795                     spritePtr = attrArr[j][0];
33796                     attPtr = attrArr[j][1];
33797                     if (attr === 'translate') {
33798                         value = {
33799                             x: attPtr.x,
33800                             y: attPtr.y
33801                         };
33802                     }
33803                     else if (attr === 'rotate') {
33804                         x = attPtr.x;
33805                         if (isNaN(x)) {
33806                             x = null;
33807                         }
33808                         y = attPtr.y;
33809                         if (isNaN(y)) {
33810                             y = null;
33811                         }
33812                         value = {
33813                             degrees: attPtr.degrees,
33814                             x: x,
33815                             y: y
33816                         };
33817                     }
33818                     else if (attr === 'width' || attr === 'height' || attr === 'x' || attr === 'y') {
33819                         value = parseFloat(attPtr);
33820                     }
33821                     else {
33822                         value = attPtr;
33823                     }
33824                     idx = Ext.Array.indexOf(spriteArr, spritePtr);
33825                     if (idx == -1) {
33826                         spriteArr.push([spritePtr, {}]);
33827                         idx = spriteArr.length - 1;
33828                     }
33829                     spriteArr[idx][1][attr] = value;
33830                 }
33831             }
33832         }
33833         ln = spriteArr.length;
33834         for (i = 0; i < ln; i++) {
33835             spritePtr = spriteArr[i];
33836             spritePtr[0].setAttributes(spritePtr[1]);
33837         }
33838         this.target.redraw();
33839     }
33840 });
33841
33842 /**
33843  * @class Ext.fx.target.CompositeSprite
33844  * @extends Ext.fx.target.Sprite
33845
33846 This class represents a animation target for a {@link Ext.draw.CompositeSprite}. It allows
33847 each {@link Ext.draw.Sprite} in the group to be animated as a whole. In general this class will not be
33848 created directly, the {@link Ext.draw.CompositeSprite} will be passed to the animation and
33849 and the appropriate target will be created.
33850
33851  * @markdown
33852  */
33853
33854 Ext.define('Ext.fx.target.CompositeSprite', {
33855
33856     /* Begin Definitions */
33857
33858     extend: 'Ext.fx.target.Sprite',
33859
33860     /* End Definitions */
33861
33862     getAttr: function(attr, val) {
33863         var out = [],
33864             target = this.target;
33865         target.each(function(sprite) {
33866             out.push([sprite, val != undefined ? val : this.getFromPrim(sprite, attr)]);
33867         }, this);
33868         return out;
33869     }
33870 });
33871
33872 /**
33873  * @class Ext.fx.target.Component
33874  * @extends Ext.fx.target.Target
33875  * 
33876  * This class represents a animation target for a {@link Ext.Component}. In general this class will not be
33877  * created directly, the {@link Ext.Component} will be passed to the animation and
33878  * and the appropriate target will be created.
33879  */
33880 Ext.define('Ext.fx.target.Component', {
33881
33882     /* Begin Definitions */
33883    
33884     extend: 'Ext.fx.target.Target',
33885     
33886     /* End Definitions */
33887
33888     type: 'component',
33889
33890     // Methods to call to retrieve unspecified "from" values from a target Component
33891     getPropMethod: {
33892         top: function() {
33893             return this.getPosition(true)[1];
33894         },
33895         left: function() {
33896             return this.getPosition(true)[0];
33897         },
33898         x: function() {
33899             return this.getPosition()[0];
33900         },
33901         y: function() {
33902             return this.getPosition()[1];
33903         },
33904         height: function() {
33905             return this.getHeight();
33906         },
33907         width: function() {
33908             return this.getWidth();
33909         },
33910         opacity: function() {
33911             return this.el.getStyle('opacity');
33912         }
33913     },
33914
33915     compMethod: {
33916         top: 'setPosition',
33917         left: 'setPosition',
33918         x: 'setPagePosition',
33919         y: 'setPagePosition',
33920         height: 'setSize',
33921         width: 'setSize',
33922         opacity: 'setOpacity'
33923     },
33924
33925     // Read the named attribute from the target Component. Use the defined getter for the attribute
33926     getAttr: function(attr, val) {
33927         return [[this.target, val !== undefined ? val : this.getPropMethod[attr].call(this.target)]];
33928     },
33929
33930     setAttr: function(targetData, isFirstFrame, isLastFrame) {
33931         var me = this,
33932             target = me.target,
33933             ln = targetData.length,
33934             attrs, attr, o, i, j, meth, targets, left, top, w, h;
33935         for (i = 0; i < ln; i++) {
33936             attrs = targetData[i].attrs;
33937             for (attr in attrs) {
33938                 targets = attrs[attr].length;
33939                 meth = {
33940                     setPosition: {},
33941                     setPagePosition: {},
33942                     setSize: {},
33943                     setOpacity: {}
33944                 };
33945                 for (j = 0; j < targets; j++) {
33946                     o = attrs[attr][j];
33947                     // We REALLY want a single function call, so push these down to merge them: eg
33948                     // meth.setPagePosition.target = <targetComponent>
33949                     // meth.setPagePosition['x'] = 100
33950                     // meth.setPagePosition['y'] = 100
33951                     meth[me.compMethod[attr]].target = o[0];
33952                     meth[me.compMethod[attr]][attr] = o[1];
33953                 }
33954                 if (meth.setPosition.target) {
33955                     o = meth.setPosition;
33956                     left = (o.left === undefined) ? undefined : parseInt(o.left, 10);
33957                     top = (o.top === undefined) ? undefined : parseInt(o.top, 10);
33958                     o.target.setPosition(left, top);
33959                 }
33960                 if (meth.setPagePosition.target) {
33961                     o = meth.setPagePosition;
33962                     o.target.setPagePosition(o.x, o.y);
33963                 }
33964                 if (meth.setSize.target) {
33965                     o = meth.setSize;
33966                     // Dimensions not being animated MUST NOT be autosized. They must remain at current value.
33967                     w = (o.width === undefined) ? o.target.getWidth() : parseInt(o.width, 10);
33968                     h = (o.height === undefined) ? o.target.getHeight() : parseInt(o.height, 10);
33969
33970                     // Only set the size of the Component on the last frame, or if the animation was
33971                     // configured with dynamic: true.
33972                     // In other cases, we just set the target element size.
33973                     // This will result in either clipping if animating a reduction in size, or the revealing of
33974                     // the inner elements of the Component if animating an increase in size.
33975                     // Component's animate function initially resizes to the larger size before resizing the
33976                     // outer element to clip the contents.
33977                     if (isLastFrame || me.dynamic) {
33978                         o.target.componentLayout.childrenChanged = true;
33979
33980                         // Flag if we are being called by an animating layout: use setCalculatedSize
33981                         if (me.layoutAnimation) {
33982                             o.target.setCalculatedSize(w, h);
33983                         } else {
33984                             o.target.setSize(w, h);
33985                         }
33986                     }
33987                     else {
33988                         o.target.el.setSize(w, h);
33989                     }
33990                 }
33991                 if (meth.setOpacity.target) {
33992                     o = meth.setOpacity;
33993                     o.target.el.setStyle('opacity', o.opacity);
33994                 }
33995             }
33996         }
33997     }
33998 });
33999
34000 /**
34001  * @class Ext.fx.CubicBezier
34002  * @ignore
34003  */
34004 Ext.define('Ext.fx.CubicBezier', {
34005
34006     /* Begin Definitions */
34007
34008     singleton: true,
34009
34010     /* End Definitions */
34011
34012     cubicBezierAtTime: function(t, p1x, p1y, p2x, p2y, duration) {
34013         var cx = 3 * p1x,
34014             bx = 3 * (p2x - p1x) - cx,
34015             ax = 1 - cx - bx,
34016             cy = 3 * p1y,
34017             by = 3 * (p2y - p1y) - cy,
34018             ay = 1 - cy - by;
34019         function sampleCurveX(t) {
34020             return ((ax * t + bx) * t + cx) * t;
34021         }
34022         function solve(x, epsilon) {
34023             var t = solveCurveX(x, epsilon);
34024             return ((ay * t + by) * t + cy) * t;
34025         }
34026         function solveCurveX(x, epsilon) {
34027             var t0, t1, t2, x2, d2, i;
34028             for (t2 = x, i = 0; i < 8; i++) {
34029                 x2 = sampleCurveX(t2) - x;
34030                 if (Math.abs(x2) < epsilon) {
34031                     return t2;
34032                 }
34033                 d2 = (3 * ax * t2 + 2 * bx) * t2 + cx;
34034                 if (Math.abs(d2) < 1e-6) {
34035                     break;
34036                 }
34037                 t2 = t2 - x2 / d2;
34038             }
34039             t0 = 0;
34040             t1 = 1;
34041             t2 = x;
34042             if (t2 < t0) {
34043                 return t0;
34044             }
34045             if (t2 > t1) {
34046                 return t1;
34047             }
34048             while (t0 < t1) {
34049                 x2 = sampleCurveX(t2);
34050                 if (Math.abs(x2 - x) < epsilon) {
34051                     return t2;
34052                 }
34053                 if (x > x2) {
34054                     t0 = t2;
34055                 } else {
34056                     t1 = t2;
34057                 }
34058                 t2 = (t1 - t0) / 2 + t0;
34059             }
34060             return t2;
34061         }
34062         return solve(t, 1 / (200 * duration));
34063     },
34064
34065     cubicBezier: function(x1, y1, x2, y2) {
34066         var fn = function(pos) {
34067             return Ext.fx.CubicBezier.cubicBezierAtTime(pos, x1, y1, x2, y2, 1);
34068         };
34069         fn.toCSS3 = function() {
34070             return 'cubic-bezier(' + [x1, y1, x2, y2].join(',') + ')';
34071         };
34072         fn.reverse = function() {
34073             return Ext.fx.CubicBezier.cubicBezier(1 - x2, 1 - y2, 1 - x1, 1 - y1);
34074         };
34075         return fn;
34076     }
34077 });
34078 /**
34079  * @class Ext.draw.Color
34080  * @extends Object
34081  *
34082  * Represents an RGB color and provides helper functions get
34083  * color components in HSL color space.
34084  */
34085 Ext.define('Ext.draw.Color', {
34086
34087     /* Begin Definitions */
34088
34089     /* End Definitions */
34090
34091     colorToHexRe: /(.*?)rgb\((\d+),\s*(\d+),\s*(\d+)\)/,
34092     rgbRe: /\s*rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)\s*/,
34093     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*/,
34094
34095     /**
34096      * @cfg {Number} lightnessFactor
34097      *
34098      * The default factor to compute the lighter or darker color. Defaults to 0.2.
34099      */
34100     lightnessFactor: 0.2,
34101
34102     /**
34103      * @constructor
34104      * @param {Number} red Red component (0..255)
34105      * @param {Number} green Green component (0..255)
34106      * @param {Number} blue Blue component (0..255)
34107      */
34108     constructor : function(red, green, blue) {
34109         var me = this,
34110             clamp = Ext.Number.constrain;
34111         me.r = clamp(red, 0, 255);
34112         me.g = clamp(green, 0, 255);
34113         me.b = clamp(blue, 0, 255);
34114     },
34115
34116     /**
34117      * Get the red component of the color, in the range 0..255.
34118      * @return {Number}
34119      */
34120     getRed: function() {
34121         return this.r;
34122     },
34123
34124     /**
34125      * Get the green component of the color, in the range 0..255.
34126      * @return {Number}
34127      */
34128     getGreen: function() {
34129         return this.g;
34130     },
34131
34132     /**
34133      * Get the blue component of the color, in the range 0..255.
34134      * @return {Number}
34135      */
34136     getBlue: function() {
34137         return this.b;
34138     },
34139
34140     /**
34141      * Get the RGB values.
34142      * @return {Array}
34143      */
34144     getRGB: function() {
34145         var me = this;
34146         return [me.r, me.g, me.b];
34147     },
34148
34149     /**
34150      * Get the equivalent HSL components of the color.
34151      * @return {Array}
34152      */
34153     getHSL: function() {
34154         var me = this,
34155             r = me.r / 255,
34156             g = me.g / 255,
34157             b = me.b / 255,
34158             max = Math.max(r, g, b),
34159             min = Math.min(r, g, b),
34160             delta = max - min,
34161             h,
34162             s = 0,
34163             l = 0.5 * (max + min);
34164
34165         // min==max means achromatic (hue is undefined)
34166         if (min != max) {
34167             s = (l < 0.5) ? delta / (max + min) : delta / (2 - max - min);
34168             if (r == max) {
34169                 h = 60 * (g - b) / delta;
34170             } else if (g == max) {
34171                 h = 120 + 60 * (b - r) / delta;
34172             } else {
34173                 h = 240 + 60 * (r - g) / delta;
34174             }
34175             if (h < 0) {
34176                 h += 360;
34177             }
34178             if (h >= 360) {
34179                 h -= 360;
34180             }
34181         }
34182         return [h, s, l];
34183     },
34184
34185     /**
34186      * Return a new color that is lighter than this color.
34187      * @param {Number} factor Lighter factor (0..1), default to 0.2
34188      * @return Ext.draw.Color
34189      */
34190     getLighter: function(factor) {
34191         var hsl = this.getHSL();
34192         factor = factor || this.lightnessFactor;
34193         hsl[2] = Ext.Number.constrain(hsl[2] + factor, 0, 1);
34194         return this.fromHSL(hsl[0], hsl[1], hsl[2]);
34195     },
34196
34197     /**
34198      * Return a new color that is darker than this color.
34199      * @param {Number} factor Darker factor (0..1), default to 0.2
34200      * @return Ext.draw.Color
34201      */
34202     getDarker: function(factor) {
34203         factor = factor || this.lightnessFactor;
34204         return this.getLighter(-factor);
34205     },
34206
34207     /**
34208      * Return the color in the hex format, i.e. '#rrggbb'.
34209      * @return {String}
34210      */
34211     toString: function() {
34212         var me = this,
34213             round = Math.round,
34214             r = round(me.r).toString(16),
34215             g = round(me.g).toString(16),
34216             b = round(me.b).toString(16);
34217         r = (r.length == 1) ? '0' + r : r;
34218         g = (g.length == 1) ? '0' + g : g;
34219         b = (b.length == 1) ? '0' + b : b;
34220         return ['#', r, g, b].join('');
34221     },
34222
34223     /**
34224      * Convert a color to hexadecimal format.
34225      *
34226      * @param {String|Array} color The color value (i.e 'rgb(255, 255, 255)', 'color: #ffffff').
34227      * Can also be an Array, in this case the function handles the first member.
34228      * @returns {String} The color in hexadecimal format.
34229      */
34230     toHex: function(color) {
34231         if (Ext.isArray(color)) {
34232             color = color[0];
34233         }
34234         if (!Ext.isString(color)) {
34235             return '';
34236         }
34237         if (color.substr(0, 1) === '#') {
34238             return color;
34239         }
34240         var digits = this.colorToHexRe.exec(color);
34241
34242         if (Ext.isArray(digits)) {
34243             var red = parseInt(digits[2], 10),
34244                 green = parseInt(digits[3], 10),
34245                 blue = parseInt(digits[4], 10),
34246                 rgb = blue | (green << 8) | (red << 16);
34247             return digits[1] + '#' + ("000000" + rgb.toString(16)).slice(-6);
34248         }
34249         else {
34250             return '';
34251         }
34252     },
34253
34254     /**
34255      * Parse the string and create a new color.
34256      *
34257      * Supported formats: '#rrggbb', '#rgb', and 'rgb(r,g,b)'.
34258      *
34259      * If the string is not recognized, an undefined will be returned instead.
34260      *
34261      * @param {String} str Color in string.
34262      * @returns Ext.draw.Color
34263      */
34264     fromString: function(str) {
34265         var values, r, g, b,
34266             parse = parseInt;
34267
34268         if ((str.length == 4 || str.length == 7) && str.substr(0, 1) === '#') {
34269             values = str.match(this.hexRe);
34270             if (values) {
34271                 r = parse(values[1], 16) >> 0;
34272                 g = parse(values[2], 16) >> 0;
34273                 b = parse(values[3], 16) >> 0;
34274                 if (str.length == 4) {
34275                     r += (r * 16);
34276                     g += (g * 16);
34277                     b += (b * 16);
34278                 }
34279             }
34280         }
34281         else {
34282             values = str.match(this.rgbRe);
34283             if (values) {
34284                 r = values[1];
34285                 g = values[2];
34286                 b = values[3];
34287             }
34288         }
34289
34290         return (typeof r == 'undefined') ? undefined : Ext.create('Ext.draw.Color', r, g, b);
34291     },
34292
34293     /**
34294      * Returns the gray value (0 to 255) of the color.
34295      *
34296      * The gray value is calculated using the formula r*0.3 + g*0.59 + b*0.11.
34297      *
34298      * @returns {Number}
34299      */
34300     getGrayscale: function() {
34301         // http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale
34302         return this.r * 0.3 + this.g * 0.59 + this.b * 0.11;
34303     },
34304
34305     /**
34306      * Create a new color based on the specified HSL values.
34307      *
34308      * @param {Number} h Hue component (0..359)
34309      * @param {Number} s Saturation component (0..1)
34310      * @param {Number} l Lightness component (0..1)
34311      * @returns Ext.draw.Color
34312      */
34313     fromHSL: function(h, s, l) {
34314         var C, X, m, i, rgb = [],
34315             abs = Math.abs,
34316             floor = Math.floor;
34317
34318         if (s == 0 || h == null) {
34319             // achromatic
34320             rgb = [l, l, l];
34321         }
34322         else {
34323             // http://en.wikipedia.org/wiki/HSL_and_HSV#From_HSL
34324             // C is the chroma
34325             // X is the second largest component
34326             // m is the lightness adjustment
34327             h /= 60;
34328             C = s * (1 - abs(2 * l - 1));
34329             X = C * (1 - abs(h - 2 * floor(h / 2) - 1));
34330             m = l - C / 2;
34331             switch (floor(h)) {
34332                 case 0:
34333                     rgb = [C, X, 0];
34334                     break;
34335                 case 1:
34336                     rgb = [X, C, 0];
34337                     break;
34338                 case 2:
34339                     rgb = [0, C, X];
34340                     break;
34341                 case 3:
34342                     rgb = [0, X, C];
34343                     break;
34344                 case 4:
34345                     rgb = [X, 0, C];
34346                     break;
34347                 case 5:
34348                     rgb = [C, 0, X];
34349                     break;
34350             }
34351             rgb = [rgb[0] + m, rgb[1] + m, rgb[2] + m];
34352         }
34353         return Ext.create('Ext.draw.Color', rgb[0] * 255, rgb[1] * 255, rgb[2] * 255);
34354     }
34355 }, function() {
34356     var prototype = this.prototype;
34357
34358     //These functions are both static and instance. TODO: find a more elegant way of copying them
34359     this.addStatics({
34360         fromHSL: function() {
34361             return prototype.fromHSL.apply(prototype, arguments);
34362         },
34363         fromString: function() {
34364             return prototype.fromString.apply(prototype, arguments);
34365         },
34366         toHex: function() {
34367             return prototype.toHex.apply(prototype, arguments);
34368         }
34369     });
34370 });
34371
34372 /**
34373  * @class Ext.dd.StatusProxy
34374  * A specialized drag proxy that supports a drop status icon, {@link Ext.Layer} styles and auto-repair.  This is the
34375  * default drag proxy used by all Ext.dd components.
34376  * @constructor
34377  * @param {Object} config
34378  */
34379 Ext.define('Ext.dd.StatusProxy', {
34380     animRepair: false,
34381
34382     constructor: function(config){
34383         Ext.apply(this, config);
34384         this.id = this.id || Ext.id();
34385         this.proxy = Ext.createWidget('component', {
34386             floating: true,
34387             id: this.id,
34388             html: '<div class="' + Ext.baseCSSPrefix + 'dd-drop-icon"></div>' +
34389                   '<div class="' + Ext.baseCSSPrefix + 'dd-drag-ghost"></div>',
34390             cls: Ext.baseCSSPrefix + 'dd-drag-proxy ' + this.dropNotAllowed,
34391             shadow: !config || config.shadow !== false,
34392             renderTo: document.body
34393         });
34394
34395         this.el = this.proxy.el;
34396         this.el.show();
34397         this.el.setVisibilityMode(Ext.core.Element.VISIBILITY);
34398         this.el.hide();
34399
34400         this.ghost = Ext.get(this.el.dom.childNodes[1]);
34401         this.dropStatus = this.dropNotAllowed;
34402     },
34403     /**
34404      * @cfg {String} dropAllowed
34405      * The CSS class to apply to the status element when drop is allowed (defaults to "x-dd-drop-ok").
34406      */
34407     dropAllowed : Ext.baseCSSPrefix + 'dd-drop-ok',
34408     /**
34409      * @cfg {String} dropNotAllowed
34410      * The CSS class to apply to the status element when drop is not allowed (defaults to "x-dd-drop-nodrop").
34411      */
34412     dropNotAllowed : Ext.baseCSSPrefix + 'dd-drop-nodrop',
34413
34414     /**
34415      * Updates the proxy's visual element to indicate the status of whether or not drop is allowed
34416      * over the current target element.
34417      * @param {String} cssClass The css class for the new drop status indicator image
34418      */
34419     setStatus : function(cssClass){
34420         cssClass = cssClass || this.dropNotAllowed;
34421         if(this.dropStatus != cssClass){
34422             this.el.replaceCls(this.dropStatus, cssClass);
34423             this.dropStatus = cssClass;
34424         }
34425     },
34426
34427     /**
34428      * Resets the status indicator to the default dropNotAllowed value
34429      * @param {Boolean} clearGhost True to also remove all content from the ghost, false to preserve it
34430      */
34431     reset : function(clearGhost){
34432         this.el.dom.className = Ext.baseCSSPrefix + 'dd-drag-proxy ' + this.dropNotAllowed;
34433         this.dropStatus = this.dropNotAllowed;
34434         if(clearGhost){
34435             this.ghost.update("");
34436         }
34437     },
34438
34439     /**
34440      * Updates the contents of the ghost element
34441      * @param {String/HTMLElement} html The html that will replace the current innerHTML of the ghost element, or a
34442      * DOM node to append as the child of the ghost element (in which case the innerHTML will be cleared first).
34443      */
34444     update : function(html){
34445         if(typeof html == "string"){
34446             this.ghost.update(html);
34447         }else{
34448             this.ghost.update("");
34449             html.style.margin = "0";
34450             this.ghost.dom.appendChild(html);
34451         }
34452         var el = this.ghost.dom.firstChild; 
34453         if(el){
34454             Ext.fly(el).setStyle('float', 'none');
34455         }
34456     },
34457
34458     /**
34459      * Returns the underlying proxy {@link Ext.Layer}
34460      * @return {Ext.Layer} el
34461     */
34462     getEl : function(){
34463         return this.el;
34464     },
34465
34466     /**
34467      * Returns the ghost element
34468      * @return {Ext.core.Element} el
34469      */
34470     getGhost : function(){
34471         return this.ghost;
34472     },
34473
34474     /**
34475      * Hides the proxy
34476      * @param {Boolean} clear True to reset the status and clear the ghost contents, false to preserve them
34477      */
34478     hide : function(clear) {
34479         this.proxy.hide();
34480         if (clear) {
34481             this.reset(true);
34482         }
34483     },
34484
34485     /**
34486      * Stops the repair animation if it's currently running
34487      */
34488     stop : function(){
34489         if(this.anim && this.anim.isAnimated && this.anim.isAnimated()){
34490             this.anim.stop();
34491         }
34492     },
34493
34494     /**
34495      * Displays this proxy
34496      */
34497     show : function() {
34498         this.proxy.show();
34499         this.proxy.toFront();
34500     },
34501
34502     /**
34503      * Force the Layer to sync its shadow and shim positions to the element
34504      */
34505     sync : function(){
34506         this.proxy.el.sync();
34507     },
34508
34509     /**
34510      * Causes the proxy to return to its position of origin via an animation.  Should be called after an
34511      * invalid drop operation by the item being dragged.
34512      * @param {Array} xy The XY position of the element ([x, y])
34513      * @param {Function} callback The function to call after the repair is complete.
34514      * @param {Object} scope The scope (<code>this</code> reference) in which the callback function is executed. Defaults to the browser window.
34515      */
34516     repair : function(xy, callback, scope){
34517         this.callback = callback;
34518         this.scope = scope;
34519         if (xy && this.animRepair !== false) {
34520             this.el.addCls(Ext.baseCSSPrefix + 'dd-drag-repair');
34521             this.el.hideUnders(true);
34522             this.anim = this.el.animate({
34523                 duration: this.repairDuration || 500,
34524                 easing: 'ease-out',
34525                 to: {
34526                     x: xy[0],
34527                     y: xy[1]
34528                 },
34529                 stopAnimation: true,
34530                 callback: this.afterRepair,
34531                 scope: this
34532             });
34533         } else {
34534             this.afterRepair();
34535         }
34536     },
34537
34538     // private
34539     afterRepair : function(){
34540         this.hide(true);
34541         if(typeof this.callback == "function"){
34542             this.callback.call(this.scope || this);
34543         }
34544         this.callback = null;
34545         this.scope = null;
34546     },
34547
34548     destroy: function(){
34549         Ext.destroy(this.ghost, this.proxy, this.el);
34550     }
34551 });
34552 /**
34553  * @class Ext.panel.Proxy
34554  * @extends Object
34555  * A custom drag proxy implementation specific to {@link Ext.panel.Panel}s. This class
34556  * is primarily used internally for the Panel's drag drop implementation, and
34557  * should never need to be created directly.
34558  * @constructor
34559  * @param panel The {@link Ext.panel.Panel} to proxy for
34560  * @param config Configuration options
34561  */
34562 Ext.define('Ext.panel.Proxy', {
34563     
34564     alternateClassName: 'Ext.dd.PanelProxy',
34565     
34566     constructor: function(panel, config){
34567         /**
34568          * @property panel
34569          * @type Ext.panel.Panel
34570          */
34571         this.panel = panel;
34572         this.id = this.panel.id +'-ddproxy';
34573         Ext.apply(this, config);
34574     },
34575
34576     /**
34577      * @cfg {Boolean} insertProxy True to insert a placeholder proxy element
34578      * while dragging the panel, false to drag with no proxy (defaults to true).
34579      * Most Panels are not absolute positioned and therefore we need to reserve
34580      * this space.
34581      */
34582     insertProxy: true,
34583
34584     // private overrides
34585     setStatus: Ext.emptyFn,
34586     reset: Ext.emptyFn,
34587     update: Ext.emptyFn,
34588     stop: Ext.emptyFn,
34589     sync: Ext.emptyFn,
34590
34591     /**
34592      * Gets the proxy's element
34593      * @return {Element} The proxy's element
34594      */
34595     getEl: function(){
34596         return this.ghost.el;
34597     },
34598
34599     /**
34600      * Gets the proxy's ghost Panel
34601      * @return {Panel} The proxy's ghost Panel
34602      */
34603     getGhost: function(){
34604         return this.ghost;
34605     },
34606
34607     /**
34608      * Gets the proxy element. This is the element that represents where the
34609      * Panel was before we started the drag operation.
34610      * @return {Element} The proxy's element
34611      */
34612     getProxy: function(){
34613         return this.proxy;
34614     },
34615
34616     /**
34617      * Hides the proxy
34618      */
34619     hide : function(){
34620         if (this.ghost) {
34621             if (this.proxy) {
34622                 this.proxy.remove();
34623                 delete this.proxy;
34624             }
34625
34626             // Unghost the Panel, do not move the Panel to where the ghost was
34627             this.panel.unghost(null, false);
34628             delete this.ghost;
34629         }
34630     },
34631
34632     /**
34633      * Shows the proxy
34634      */
34635     show: function(){
34636         if (!this.ghost) {
34637             var panelSize = this.panel.getSize();
34638             this.panel.el.setVisibilityMode(Ext.core.Element.DISPLAY);
34639             this.ghost = this.panel.ghost();
34640             if (this.insertProxy) {
34641                 // bc Panels aren't absolute positioned we need to take up the space
34642                 // of where the panel previously was
34643                 this.proxy = this.panel.el.insertSibling({cls: Ext.baseCSSPrefix + 'panel-dd-spacer'});
34644                 this.proxy.setSize(panelSize);
34645             }
34646         }
34647     },
34648
34649     // private
34650     repair: function(xy, callback, scope) {
34651         this.hide();
34652         if (typeof callback == "function") {
34653             callback.call(scope || this);
34654         }
34655     },
34656
34657     /**
34658      * Moves the proxy to a different position in the DOM.  This is typically
34659      * called while dragging the Panel to keep the proxy sync'd to the Panel's
34660      * location.
34661      * @param {HTMLElement} parentNode The proxy's parent DOM node
34662      * @param {HTMLElement} before (optional) The sibling node before which the
34663      * proxy should be inserted (defaults to the parent's last child if not
34664      * specified)
34665      */
34666     moveProxy : function(parentNode, before){
34667         if (this.proxy) {
34668             parentNode.insertBefore(this.proxy.dom, before);
34669         }
34670     }
34671 });
34672 /**
34673  * @class Ext.layout.component.AbstractDock
34674  * @extends Ext.layout.component.Component
34675  * @private
34676  * This ComponentLayout handles docking for Panels. It takes care of panels that are
34677  * part of a ContainerLayout that sets this Panel's size and Panels that are part of
34678  * an AutoContainerLayout in which this panel get his height based of the CSS or
34679  * or its content.
34680  */
34681
34682 Ext.define('Ext.layout.component.AbstractDock', {
34683
34684     /* Begin Definitions */
34685
34686     extend: 'Ext.layout.component.Component',
34687
34688     /* End Definitions */
34689
34690     type: 'dock',
34691
34692     /**
34693      * @private
34694      * @property autoSizing
34695      * @type boolean
34696      * This flag is set to indicate this layout may have an autoHeight/autoWidth.
34697      */
34698     autoSizing: true,
34699
34700     beforeLayout: function() {
34701         var returnValue = this.callParent(arguments);
34702         if (returnValue !== false && (!this.initializedBorders || this.childrenChanged) && (!this.owner.border || this.owner.manageBodyBorders)) {
34703             this.handleItemBorders();
34704             this.initializedBorders = true;
34705         }
34706         return returnValue;
34707     },
34708     
34709     handleItemBorders: function() {
34710         var owner = this.owner,
34711             body = owner.body,
34712             docked = this.getLayoutItems(),
34713             borders = {
34714                 top: [],
34715                 right: [],
34716                 bottom: [],
34717                 left: []
34718             },
34719             oldBorders = this.borders,
34720             opposites = {
34721                 top: 'bottom',
34722                 right: 'left',
34723                 bottom: 'top',
34724                 left: 'right'
34725             },
34726             i, ln, item, dock, side;
34727
34728         for (i = 0, ln = docked.length; i < ln; i++) {
34729             item = docked[i];
34730             dock = item.dock;
34731             
34732             if (item.ignoreBorderManagement) {
34733                 continue;
34734             }
34735             
34736             if (!borders[dock].satisfied) {
34737                 borders[dock].push(item);
34738                 borders[dock].satisfied = true;
34739             }
34740             
34741             if (!borders.top.satisfied && opposites[dock] !== 'top') {
34742                 borders.top.push(item);
34743             }
34744             if (!borders.right.satisfied && opposites[dock] !== 'right') {
34745                 borders.right.push(item);
34746             }            
34747             if (!borders.bottom.satisfied && opposites[dock] !== 'bottom') {
34748                 borders.bottom.push(item);
34749             }            
34750             if (!borders.left.satisfied && opposites[dock] !== 'left') {
34751                 borders.left.push(item);
34752             }
34753         }
34754
34755         if (oldBorders) {
34756             for (side in oldBorders) {
34757                 if (oldBorders.hasOwnProperty(side)) {
34758                     ln = oldBorders[side].length;
34759                     if (!owner.manageBodyBorders) {
34760                         for (i = 0; i < ln; i++) {
34761                             oldBorders[side][i].removeCls(Ext.baseCSSPrefix + 'docked-noborder-' + side);
34762                         }
34763                         if (!oldBorders[side].satisfied && !owner.bodyBorder) {
34764                             body.removeCls(Ext.baseCSSPrefix + 'docked-noborder-' + side);                   
34765                         }                    
34766                     }
34767                     else if (oldBorders[side].satisfied) {
34768                         body.setStyle('border-' + side + '-width', '');
34769                     }
34770                 }
34771             }
34772         }
34773                 
34774         for (side in borders) {
34775             if (borders.hasOwnProperty(side)) {
34776                 ln = borders[side].length;
34777                 if (!owner.manageBodyBorders) {
34778                     for (i = 0; i < ln; i++) {
34779                         borders[side][i].addCls(Ext.baseCSSPrefix + 'docked-noborder-' + side);
34780                     }
34781                     if ((!borders[side].satisfied && !owner.bodyBorder) || owner.bodyBorder === false) {
34782                         body.addCls(Ext.baseCSSPrefix + 'docked-noborder-' + side);                   
34783                     }                    
34784                 }
34785                 else if (borders[side].satisfied) {
34786                     body.setStyle('border-' + side + '-width', '1px');
34787                 }
34788             }
34789         }
34790         
34791         this.borders = borders;
34792     },
34793     
34794     /**
34795      * @protected
34796      * @param {Ext.Component} owner The Panel that owns this DockLayout
34797      * @param {Ext.core.Element} target The target in which we are going to render the docked items
34798      * @param {Array} args The arguments passed to the ComponentLayout.layout method
34799      */
34800     onLayout: function(width, height) {
34801         var me = this,
34802             owner = me.owner,
34803             body = owner.body,
34804             layout = owner.layout,
34805             target = me.getTarget(),
34806             autoWidth = false,
34807             autoHeight = false,
34808             padding, border, frameSize;
34809
34810         // We start of by resetting all the layouts info
34811         var info = me.info = {
34812             boxes: [],
34813             size: {
34814                 width: width,
34815                 height: height
34816             },
34817             bodyBox: {}
34818         };
34819
34820         Ext.applyIf(info, me.getTargetInfo());
34821
34822         // We need to bind to the ownerCt whenever we do not have a user set height or width.
34823         if (owner && owner.ownerCt && owner.ownerCt.layout && owner.ownerCt.layout.isLayout) {
34824             if (!Ext.isNumber(owner.height) || !Ext.isNumber(owner.width)) {
34825                 owner.ownerCt.layout.bindToOwnerCtComponent = true;
34826             }
34827             else {
34828                 owner.ownerCt.layout.bindToOwnerCtComponent = false;
34829             }
34830         }
34831
34832         // Determine if we have an autoHeight or autoWidth.
34833         if (height === undefined || height === null || width === undefined || width === null) {
34834             padding = info.padding;
34835             border = info.border;
34836             frameSize = me.frameSize;
34837
34838             // Auto-everything, clear out any style height/width and read from css
34839             if ((height === undefined || height === null) && (width === undefined || width === null)) {
34840                 autoHeight = true;
34841                 autoWidth = true;
34842                 me.setTargetSize(null);
34843                 me.setBodyBox({width: null, height: null});
34844             }
34845             // Auto-height
34846             else if (height === undefined || height === null) {
34847                 autoHeight = true;
34848                 // Clear any sizing that we already set in a previous layout
34849                 me.setTargetSize(width);
34850                 me.setBodyBox({width: width - padding.left - border.left - padding.right - border.right - frameSize.left - frameSize.right, height: null});
34851             // Auto-width
34852             }
34853             else {
34854                 autoWidth = true;
34855                 // Clear any sizing that we already set in a previous layout
34856                 me.setTargetSize(null, height);
34857                 me.setBodyBox({width: null, height: height - padding.top - padding.bottom - border.top - border.bottom - frameSize.top - frameSize.bottom});
34858             }
34859
34860             // Run the container
34861             if (layout && layout.isLayout) {
34862                 // Auto-Sized so have the container layout notify the component layout.
34863                 layout.bindToOwnerCtComponent = true;
34864                 layout.layout();
34865
34866                 // If this is an autosized container layout, then we must compensate for a
34867                 // body that is being autosized.  We do not want to adjust the body's size
34868                 // to accommodate the dock items, but rather we will want to adjust the
34869                 // target's size.
34870                 //
34871                 // This is necessary because, particularly in a Box layout, all child items
34872                 // are set with absolute dimensions that are not flexible to the size of its
34873                 // innerCt/target.  So once they are laid out, they are sized for good. By
34874                 // shrinking the body box to accommodate dock items, we're merely cutting off
34875                 // parts of the body.  Not good.  Instead, the target's size should expand
34876                 // to fit the dock items in.  This is valid because the target container is
34877                 // suppose to be autosized to fit everything accordingly.
34878                 info.autoSizedCtLayout = layout.autoSize === true;
34879             }
34880
34881             // The dockItems method will add all the top and bottom docked items height
34882             // to the info.panelSize height. That's why we have to call setSize after
34883             // we dock all the items to actually set the panel's width and height.
34884             // We have to do this because the panel body and docked items will be position
34885             // absolute which doesn't stretch the panel.
34886             me.dockItems(autoWidth, autoHeight);
34887             me.setTargetSize(info.size.width, info.size.height);
34888         }
34889         else {
34890             me.setTargetSize(width, height);
34891             me.dockItems();
34892         }
34893         me.callParent(arguments);
34894     },
34895
34896     /**
34897      * @protected
34898      * This method will first update all the information about the docked items,
34899      * body dimensions and position, the panel's total size. It will then
34900      * set all these values on the docked items and panel body.
34901      * @param {Array} items Array containing all the docked items
34902      * @param {Boolean} autoBoxes Set this to true if the Panel is part of an
34903      * AutoContainerLayout
34904      */
34905     dockItems : function(autoWidth, autoHeight) {
34906         this.calculateDockBoxes(autoWidth, autoHeight);
34907
34908         // Both calculateAutoBoxes and calculateSizedBoxes are changing the
34909         // information about the body, panel size, and boxes for docked items
34910         // inside a property called info.
34911         var info = this.info,
34912             boxes = info.boxes,
34913             ln = boxes.length,
34914             dock, i;
34915
34916         // We are going to loop over all the boxes that were calculated
34917         // and set the position of each item the box belongs to.
34918         for (i = 0; i < ln; i++) {
34919             dock = boxes[i];
34920             dock.item.setPosition(dock.x, dock.y);
34921             if ((autoWidth || autoHeight) && dock.layout && dock.layout.isLayout) {
34922                 // Auto-Sized so have the container layout notify the component layout.
34923                 dock.layout.bindToOwnerCtComponent = true;
34924             }
34925         }
34926
34927         // Don't adjust body width/height if the target is using an auto container layout.
34928         // But, we do want to adjust the body size if the container layout is auto sized.
34929         if (!info.autoSizedCtLayout) {
34930             if (autoWidth) {
34931                 info.bodyBox.width = null;
34932             }
34933             if (autoHeight) {
34934                 info.bodyBox.height = null;
34935             }
34936         }
34937
34938         // If the bodyBox has been adjusted because of the docked items
34939         // we will update the dimensions and position of the panel's body.
34940         this.setBodyBox(info.bodyBox);
34941     },
34942
34943     /**
34944      * @protected
34945      * This method will set up some initial information about the panel size and bodybox
34946      * and then loop over all the items you pass it to take care of stretching, aligning,
34947      * dock position and all calculations involved with adjusting the body box.
34948      * @param {Array} items Array containing all the docked items we have to layout
34949      */
34950     calculateDockBoxes : function(autoWidth, autoHeight) {
34951         // We want to use the Panel's el width, and the Panel's body height as the initial
34952         // size we are going to use in calculateDockBoxes. We also want to account for
34953         // the border of the panel.
34954         var me = this,
34955             target = me.getTarget(),
34956             items = me.getLayoutItems(),
34957             owner = me.owner,
34958             bodyEl = owner.body,
34959             info = me.info,
34960             size = info.size,
34961             ln = items.length,
34962             padding = info.padding,
34963             border = info.border,
34964             frameSize = me.frameSize,
34965             item, i, box, rect;
34966
34967         // If this Panel is inside an AutoContainerLayout, we will base all the calculations
34968         // around the height of the body and the width of the panel.
34969         if (autoHeight) {
34970             size.height = bodyEl.getHeight() + padding.top + border.top + padding.bottom + border.bottom + frameSize.top + frameSize.bottom;
34971         }
34972         else {
34973             size.height = target.getHeight();
34974         }
34975         if (autoWidth) {
34976             size.width = bodyEl.getWidth() + padding.left + border.left + padding.right + border.right + frameSize.left + frameSize.right;
34977         }
34978         else {
34979             size.width = target.getWidth();
34980         }
34981
34982         info.bodyBox = {
34983             x: padding.left + frameSize.left,
34984             y: padding.top + frameSize.top,
34985             width: size.width - padding.left - border.left - padding.right - border.right - frameSize.left - frameSize.right,
34986             height: size.height - border.top - padding.top - border.bottom - padding.bottom - frameSize.top - frameSize.bottom
34987         };
34988
34989         // Loop over all the docked items
34990         for (i = 0; i < ln; i++) {
34991             item = items[i];
34992             // The initBox method will take care of stretching and alignment
34993             // In some cases it will also layout the dock items to be able to
34994             // get a width or height measurement
34995             box = me.initBox(item);
34996
34997             if (autoHeight === true) {
34998                 box = me.adjustAutoBox(box, i);
34999             }
35000             else {
35001                 box = me.adjustSizedBox(box, i);
35002             }
35003
35004             // Save our box. This allows us to loop over all docked items and do all
35005             // calculations first. Then in one loop we will actually size and position
35006             // all the docked items that have changed.
35007             info.boxes.push(box);
35008         }
35009     },
35010
35011     /**
35012      * @protected
35013      * This method will adjust the position of the docked item and adjust the body box
35014      * accordingly.
35015      * @param {Object} box The box containing information about the width and height
35016      * of this docked item
35017      * @param {Number} index The index position of this docked item
35018      * @return {Object} The adjusted box
35019      */
35020     adjustSizedBox : function(box, index) {
35021         var bodyBox = this.info.bodyBox,
35022             frameSize = this.frameSize,
35023             info = this.info,
35024             padding = info.padding,
35025             pos = box.type,
35026             border = info.border;
35027
35028         switch (pos) {
35029             case 'top':
35030                 box.y = bodyBox.y;
35031                 break;
35032
35033             case 'left':
35034                 box.x = bodyBox.x;
35035                 break;
35036
35037             case 'bottom':
35038                 box.y = (bodyBox.y + bodyBox.height) - box.height;
35039                 break;
35040
35041             case 'right':
35042                 box.x = (bodyBox.x + bodyBox.width) - box.width;
35043                 break;
35044         }
35045
35046         if (box.ignoreFrame) {
35047             if (pos == 'bottom') {
35048                 box.y += (frameSize.bottom + padding.bottom + border.bottom);
35049             }
35050             else {
35051                 box.y -= (frameSize.top + padding.top + border.top);
35052             }
35053             if (pos == 'right') {
35054                 box.x += (frameSize.right + padding.right + border.right);
35055             }
35056             else {
35057                 box.x -= (frameSize.left + padding.left + border.left);
35058             }
35059         }
35060
35061         // If this is not an overlaying docked item, we have to adjust the body box
35062         if (!box.overlay) {
35063             switch (pos) {
35064                 case 'top':
35065                     bodyBox.y += box.height;
35066                     bodyBox.height -= box.height;
35067                     break;
35068
35069                 case 'left':
35070                     bodyBox.x += box.width;
35071                     bodyBox.width -= box.width;
35072                     break;
35073
35074                 case 'bottom':
35075                     bodyBox.height -= box.height;
35076                     break;
35077
35078                 case 'right':
35079                     bodyBox.width -= box.width;
35080                     break;
35081             }
35082         }
35083         return box;
35084     },
35085
35086     /**
35087      * @protected
35088      * This method will adjust the position of the docked item inside an AutoContainerLayout
35089      * and adjust the body box accordingly.
35090      * @param {Object} box The box containing information about the width and height
35091      * of this docked item
35092      * @param {Number} index The index position of this docked item
35093      * @return {Object} The adjusted box
35094      */
35095     adjustAutoBox : function (box, index) {
35096         var info = this.info,
35097             bodyBox = info.bodyBox,
35098             size = info.size,
35099             boxes = info.boxes,
35100             boxesLn = boxes.length,
35101             pos = box.type,
35102             frameSize = this.frameSize,
35103             padding = info.padding,
35104             border = info.border,
35105             autoSizedCtLayout = info.autoSizedCtLayout,
35106             ln = (boxesLn < index) ? boxesLn : index,
35107             i, adjustBox;
35108
35109         if (pos == 'top' || pos == 'bottom') {
35110             // This can affect the previously set left and right and bottom docked items
35111             for (i = 0; i < ln; i++) {
35112                 adjustBox = boxes[i];
35113                 if (adjustBox.stretched && adjustBox.type == 'left' || adjustBox.type == 'right') {
35114                     adjustBox.height += box.height;
35115                 }
35116                 else if (adjustBox.type == 'bottom') {
35117                     adjustBox.y += box.height;
35118                 }
35119             }
35120         }
35121
35122         switch (pos) {
35123             case 'top':
35124                 box.y = bodyBox.y;
35125                 if (!box.overlay) {
35126                     bodyBox.y += box.height;
35127                 }
35128                 size.height += box.height;
35129                 break;
35130
35131             case 'bottom':
35132                 box.y = (bodyBox.y + bodyBox.height);
35133                 size.height += box.height;
35134                 break;
35135
35136             case 'left':
35137                 box.x = bodyBox.x;
35138                 if (!box.overlay) {
35139                     bodyBox.x += box.width;
35140                     if (autoSizedCtLayout) {
35141                         size.width += box.width;
35142                     } else {
35143                         bodyBox.width -= box.width;
35144                     }
35145                 }
35146                 break;
35147
35148             case 'right':
35149                 if (!box.overlay) {
35150                     if (autoSizedCtLayout) {
35151                         size.width += box.width;
35152                     } else {
35153                         bodyBox.width -= box.width;
35154                     }
35155                 }
35156                 box.x = (bodyBox.x + bodyBox.width);
35157                 break;
35158         }
35159
35160         if (box.ignoreFrame) {
35161             if (pos == 'bottom') {
35162                 box.y += (frameSize.bottom + padding.bottom + border.bottom);
35163             }
35164             else {
35165                 box.y -= (frameSize.top + padding.top + border.top);
35166             }
35167             if (pos == 'right') {
35168                 box.x += (frameSize.right + padding.right + border.right);
35169             }
35170             else {
35171                 box.x -= (frameSize.left + padding.left + border.left);
35172             }
35173         }
35174         return box;
35175     },
35176
35177     /**
35178      * @protected
35179      * This method will create a box object, with a reference to the item, the type of dock
35180      * (top, left, bottom, right). It will also take care of stretching and aligning of the
35181      * docked items.
35182      * @param {Ext.Component} item The docked item we want to initialize the box for
35183      * @return {Object} The initial box containing width and height and other useful information
35184      */
35185     initBox : function(item) {
35186         var me = this,
35187             bodyBox = me.info.bodyBox,
35188             horizontal = (item.dock == 'top' || item.dock == 'bottom'),
35189             owner = me.owner,
35190             frameSize = me.frameSize,
35191             info = me.info,
35192             padding = info.padding,
35193             border = info.border,
35194             box = {
35195                 item: item,
35196                 overlay: item.overlay,
35197                 type: item.dock,
35198                 offsets: Ext.core.Element.parseBox(item.offsets || {}),
35199                 ignoreFrame: item.ignoreParentFrame
35200             };
35201         // First we are going to take care of stretch and align properties for all four dock scenarios.
35202         if (item.stretch !== false) {
35203             box.stretched = true;
35204             if (horizontal) {
35205                 box.x = bodyBox.x + box.offsets.left;
35206                 box.width = bodyBox.width - (box.offsets.left + box.offsets.right);
35207                 if (box.ignoreFrame) {
35208                     box.width += (frameSize.left + frameSize.right + border.left + border.right + padding.left + padding.right);
35209                 }
35210                 item.setCalculatedSize(box.width - item.el.getMargin('lr'), undefined, owner);
35211             }
35212             else {
35213                 box.y = bodyBox.y + box.offsets.top;
35214                 box.height = bodyBox.height - (box.offsets.bottom + box.offsets.top);
35215                 if (box.ignoreFrame) {
35216                     box.height += (frameSize.top + frameSize.bottom + border.top + border.bottom + padding.top + padding.bottom);
35217                 }
35218                 item.setCalculatedSize(undefined, box.height - item.el.getMargin('tb'), owner);
35219
35220                 // At this point IE will report the left/right-docked toolbar as having a width equal to the
35221                 // container's full width. Forcing a repaint kicks it into shape so it reports the correct width.
35222                 if (!Ext.supports.ComputedStyle) {
35223                     item.el.repaint();
35224                 }
35225             }
35226         }
35227         else {
35228             item.doComponentLayout();
35229             box.width = item.getWidth() - (box.offsets.left + box.offsets.right);
35230             box.height = item.getHeight() - (box.offsets.bottom + box.offsets.top);
35231             box.y += box.offsets.top;
35232             if (horizontal) {
35233                 box.x = (item.align == 'right') ? bodyBox.width - box.width : bodyBox.x;
35234                 box.x += box.offsets.left;
35235             }
35236         }
35237
35238         // If we haven't calculated the width or height of the docked item yet
35239         // do so, since we need this for our upcoming calculations
35240         if (box.width == undefined) {
35241             box.width = item.getWidth() + item.el.getMargin('lr');
35242         }
35243         if (box.height == undefined) {
35244             box.height = item.getHeight() + item.el.getMargin('tb');
35245         }
35246
35247         return box;
35248     },
35249
35250     /**
35251      * @protected
35252      * Returns an array containing all the <b>visible</b> docked items inside this layout's owner Panel
35253      * @return {Array} An array containing all the <b>visible</b> docked items of the Panel
35254      */
35255     getLayoutItems : function() {
35256         var it = this.owner.getDockedItems(),
35257             ln = it.length,
35258             i = 0,
35259             result = [];
35260         for (; i < ln; i++) {
35261             if (it[i].isVisible(true)) {
35262                 result.push(it[i]);
35263             }
35264         }
35265         return result;
35266     },
35267
35268     /**
35269      * @protected
35270      * Render the top and left docked items before any existing DOM nodes in our render target,
35271      * and then render the right and bottom docked items after. This is important, for such things
35272      * as tab stops and ARIA readers, that the DOM nodes are in a meaningful order.
35273      * Our collection of docked items will already be ordered via Panel.getDockedItems().
35274      */
35275     renderItems: function(items, target) {
35276         var cns = target.dom.childNodes,
35277             cnsLn = cns.length,
35278             ln = items.length,
35279             domLn = 0,
35280             i, j, cn, item;
35281
35282         // Calculate the number of DOM nodes in our target that are not our docked items
35283         for (i = 0; i < cnsLn; i++) {
35284             cn = Ext.get(cns[i]);
35285             for (j = 0; j < ln; j++) {
35286                 item = items[j];
35287                 if (item.rendered && (cn.id == item.el.id || cn.down('#' + item.el.id))) {
35288                     break;
35289                 }
35290             }
35291
35292             if (j === ln) {
35293                 domLn++;
35294             }
35295         }
35296
35297         // Now we go through our docked items and render/move them
35298         for (i = 0, j = 0; i < ln; i++, j++) {
35299             item = items[i];
35300
35301             // If we're now at the right/bottom docked item, we jump ahead in our
35302             // DOM position, just past the existing DOM nodes.
35303             //
35304             // TODO: This is affected if users provide custom weight values to their
35305             // docked items, which puts it out of (t,l,r,b) order. Avoiding a second
35306             // sort operation here, for now, in the name of performance. getDockedItems()
35307             // needs the sort operation not just for this layout-time rendering, but
35308             // also for getRefItems() to return a logical ordering (FocusManager, CQ, et al).
35309             if (i === j && (item.dock === 'right' || item.dock === 'bottom')) {
35310                 j += domLn;
35311             }
35312
35313             // Same logic as Layout.renderItems()
35314             if (item && !item.rendered) {
35315                 this.renderItem(item, target, j);
35316             }
35317             else if (!this.isValidParent(item, target, j)) {
35318                 this.moveItem(item, target, j);
35319             }
35320         }
35321     },
35322
35323     /**
35324      * @protected
35325      * This function will be called by the dockItems method. Since the body is positioned absolute,
35326      * we need to give it dimensions and a position so that it is in the middle surrounded by
35327      * docked items
35328      * @param {Object} box An object containing new x, y, width and height values for the
35329      * Panel's body
35330      */
35331     setBodyBox : function(box) {
35332         var me = this,
35333             owner = me.owner,
35334             body = owner.body,
35335             info = me.info,
35336             bodyMargin = info.bodyMargin,
35337             padding = info.padding,
35338             border = info.border,
35339             frameSize = me.frameSize;
35340         
35341         // Panel collapse effectively hides the Panel's body, so this is a no-op.
35342         if (owner.collapsed) {
35343             return;
35344         }
35345         
35346         if (Ext.isNumber(box.width)) {
35347             box.width -= bodyMargin.left + bodyMargin.right;
35348         }
35349         
35350         if (Ext.isNumber(box.height)) {
35351             box.height -= bodyMargin.top + bodyMargin.bottom;
35352         }
35353         
35354         me.setElementSize(body, box.width, box.height);
35355         if (Ext.isNumber(box.x)) {
35356             body.setLeft(box.x - padding.left - frameSize.left);
35357         }
35358         if (Ext.isNumber(box.y)) {
35359             body.setTop(box.y - padding.top - frameSize.top);
35360         }
35361     },
35362
35363     /**
35364      * @protected
35365      * We are overriding the Ext.layout.Layout configureItem method to also add a class that
35366      * indicates the position of the docked item. We use the itemCls (x-docked) as a prefix.
35367      * An example of a class added to a dock: right item is x-docked-right
35368      * @param {Ext.Component} item The item we are configuring
35369      */
35370     configureItem : function(item, pos) {
35371         this.callParent(arguments);
35372         
35373         item.addCls(Ext.baseCSSPrefix + 'docked');
35374         item.addClsWithUI('docked-' + item.dock);
35375     },
35376
35377     afterRemove : function(item) {
35378         this.callParent(arguments);
35379         if (this.itemCls) {
35380             item.el.removeCls(this.itemCls + '-' + item.dock);
35381         }
35382         var dom = item.el.dom;
35383
35384         if (!item.destroying && dom) {
35385             dom.parentNode.removeChild(dom);
35386         }
35387         this.childrenChanged = true;
35388     }
35389 });
35390 /**
35391  * @class Ext.app.EventBus
35392  * @private
35393  *
35394  * Class documentation for the MVC classes will be present before 4.0 final, in the mean time please refer to the MVC
35395  * guide
35396  */
35397 Ext.define('Ext.app.EventBus', {
35398     requires: [
35399         'Ext.util.Event'
35400     ],
35401     mixins: {
35402         observable: 'Ext.util.Observable'
35403     },
35404     
35405     constructor: function() {
35406         this.mixins.observable.constructor.call(this);
35407         
35408         this.bus = {};
35409         
35410         var me = this;
35411         Ext.override(Ext.Component, {
35412             fireEvent: function(ev) {
35413                 if (Ext.util.Observable.prototype.fireEvent.apply(this, arguments) !== false) {
35414                     return me.dispatch.call(me, ev, this, arguments);
35415                 }
35416                 return false;
35417             }
35418         });
35419     },
35420
35421     dispatch: function(ev, target, args) {
35422         var bus = this.bus,
35423             selectors = bus[ev],
35424             selector, controllers, id, events, event, i, ln;
35425         
35426         if (selectors) {
35427             // Loop over all the selectors that are bound to this event
35428             for (selector in selectors) {
35429                 // Check if the target matches the selector
35430                 if (target.is(selector)) {
35431                     // Loop over all the controllers that are bound to this selector   
35432                     controllers = selectors[selector];
35433                     for (id in controllers) {
35434                         // Loop over all the events that are bound to this selector on this controller
35435                         events = controllers[id];
35436                         for (i = 0, ln = events.length; i < ln; i++) {
35437                             event = events[i];
35438                             // Fire the event!
35439                             return event.fire.apply(event, Array.prototype.slice.call(args, 1));
35440                         }
35441                     }
35442                 }
35443             }
35444         }
35445     },
35446     
35447     control: function(selectors, listeners, controller) {
35448         var bus = this.bus,
35449             selector, fn;
35450         
35451         if (Ext.isString(selectors)) {
35452             selector = selectors;
35453             selectors = {};
35454             selectors[selector] = listeners;
35455             this.control(selectors, null, controller);
35456             return;
35457         }
35458     
35459         Ext.Object.each(selectors, function(selector, listeners) {
35460             Ext.Object.each(listeners, function(ev, listener) {
35461                 var options = {},   
35462                     scope = controller,
35463                     event = Ext.create('Ext.util.Event', controller, ev);
35464                 
35465                 // Normalize the listener                
35466                 if (Ext.isObject(listener)) {
35467                     options = listener;
35468                     listener = options.fn;
35469                     scope = options.scope || controller;
35470                     delete options.fn;
35471                     delete options.scope;
35472                 }
35473                 
35474                 event.addListener(listener, scope, options);
35475
35476                 // Create the bus tree if it is not there yet
35477                 bus[ev] = bus[ev] || {};
35478                 bus[ev][selector] = bus[ev][selector] || {};
35479                 bus[ev][selector][controller.id] = bus[ev][selector][controller.id] || [];            
35480                 
35481                 // Push our listener in our bus
35482                 bus[ev][selector][controller.id].push(event);
35483             });
35484         });
35485     }
35486 });
35487 /**
35488  * @class Ext.data.Types
35489  * <p>This is s static class containing the system-supplied data types which may be given to a {@link Ext.data.Field Field}.<p/>
35490  * <p>The properties in this class are used as type indicators in the {@link Ext.data.Field Field} class, so to
35491  * test whether a Field is of a certain type, compare the {@link Ext.data.Field#type type} property against properties
35492  * of this class.</p>
35493  * <p>Developers may add their own application-specific data types to this class. Definition names must be UPPERCASE.
35494  * each type definition must contain three properties:</p>
35495  * <div class="mdetail-params"><ul>
35496  * <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
35497  * to be stored in the Field. The function is passed the collowing parameters:
35498  * <div class="mdetail-params"><ul>
35499  * <li><b>v</b> : Mixed<div class="sub-desc">The data value as read by the Reader, if undefined will use
35500  * the configured <tt>{@link Ext.data.Field#defaultValue defaultValue}</tt>.</div></li>
35501  * <li><b>rec</b> : Mixed<div class="sub-desc">The data object containing the row as read by the Reader.
35502  * Depending on the Reader type, this could be an Array ({@link Ext.data.reader.Array ArrayReader}), an object
35503  * ({@link Ext.data.reader.Json JsonReader}), or an XML element.</div></li>
35504  * </ul></div></div></li>
35505  * <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>
35506  * <li><code>type</code> : <i>String</i> <div class="sub-desc">A textual data type name.</div></li>
35507  * </ul></div>
35508  * <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
35509  * which contained the properties <code>lat</code> and <code>long</code>, you would define a new data type like this:</p>
35510  *<pre><code>
35511 // Add a new Field data type which stores a VELatLong object in the Record.
35512 Ext.data.Types.VELATLONG = {
35513     convert: function(v, data) {
35514         return new VELatLong(data.lat, data.long);
35515     },
35516     sortType: function(v) {
35517         return v.Latitude;  // When sorting, order by latitude
35518     },
35519     type: 'VELatLong'
35520 };
35521 </code></pre>
35522  * <p>Then, when declaring a Model, use <pre><code>
35523 var types = Ext.data.Types; // allow shorthand type access
35524 Ext.define('Unit',
35525     extend: 'Ext.data.Model', 
35526     fields: [
35527         { name: 'unitName', mapping: 'UnitName' },
35528         { name: 'curSpeed', mapping: 'CurSpeed', type: types.INT },
35529         { name: 'latitude', mapping: 'lat', type: types.FLOAT },
35530         { name: 'latitude', mapping: 'lat', type: types.FLOAT },
35531         { name: 'position', type: types.VELATLONG }
35532     ]
35533 });
35534 </code></pre>
35535  * @singleton
35536  */
35537 Ext.define('Ext.data.Types', {
35538     singleton: true,
35539     requires: ['Ext.data.SortTypes']
35540 }, function() {
35541     var st = Ext.data.SortTypes;
35542     
35543     Ext.apply(Ext.data.Types, {
35544         /**
35545          * @type Regexp
35546          * @property stripRe
35547          * A regular expression for stripping non-numeric characters from a numeric value. Defaults to <tt>/[\$,%]/g</tt>.
35548          * This should be overridden for localization.
35549          */
35550         stripRe: /[\$,%]/g,
35551         
35552         /**
35553          * @type Object.
35554          * @property AUTO
35555          * This data type means that no conversion is applied to the raw data before it is placed into a Record.
35556          */
35557         AUTO: {
35558             convert: function(v) {
35559                 return v;
35560             },
35561             sortType: st.none,
35562             type: 'auto'
35563         },
35564
35565         /**
35566          * @type Object.
35567          * @property STRING
35568          * This data type means that the raw data is converted into a String before it is placed into a Record.
35569          */
35570         STRING: {
35571             convert: function(v) {
35572                 var defaultValue = this.useNull ? null : '';
35573                 return (v === undefined || v === null) ? defaultValue : String(v);
35574             },
35575             sortType: st.asUCString,
35576             type: 'string'
35577         },
35578
35579         /**
35580          * @type Object.
35581          * @property INT
35582          * This data type means that the raw data is converted into an integer before it is placed into a Record.
35583          * <p>The synonym <code>INTEGER</code> is equivalent.</p>
35584          */
35585         INT: {
35586             convert: function(v) {
35587                 return v !== undefined && v !== null && v !== '' ?
35588                     parseInt(String(v).replace(Ext.data.Types.stripRe, ''), 10) : (this.useNull ? null : 0);
35589             },
35590             sortType: st.none,
35591             type: 'int'
35592         },
35593         
35594         /**
35595          * @type Object.
35596          * @property FLOAT
35597          * This data type means that the raw data is converted into a number before it is placed into a Record.
35598          * <p>The synonym <code>NUMBER</code> is equivalent.</p>
35599          */
35600         FLOAT: {
35601             convert: function(v) {
35602                 return v !== undefined && v !== null && v !== '' ?
35603                     parseFloat(String(v).replace(Ext.data.Types.stripRe, ''), 10) : (this.useNull ? null : 0);
35604             },
35605             sortType: st.none,
35606             type: 'float'
35607         },
35608         
35609         /**
35610          * @type Object.
35611          * @property BOOL
35612          * <p>This data type means that the raw data is converted into a boolean before it is placed into
35613          * a Record. The string "true" and the number 1 are converted to boolean <code>true</code>.</p>
35614          * <p>The synonym <code>BOOLEAN</code> is equivalent.</p>
35615          */
35616         BOOL: {
35617             convert: function(v) {
35618                 if (this.useNull && v === undefined || v === null || v === '') {
35619                     return null;
35620                 }
35621                 return v === true || v === 'true' || v == 1;
35622             },
35623             sortType: st.none,
35624             type: 'bool'
35625         },
35626         
35627         /**
35628          * @type Object.
35629          * @property DATE
35630          * This data type means that the raw data is converted into a Date before it is placed into a Record.
35631          * The date format is specified in the constructor of the {@link Ext.data.Field} to which this type is
35632          * being applied.
35633          */
35634         DATE: {
35635             convert: function(v) {
35636                 var df = this.dateFormat;
35637                 if (!v) {
35638                     return null;
35639                 }
35640                 if (Ext.isDate(v)) {
35641                     return v;
35642                 }
35643                 if (df) {
35644                     if (df == 'timestamp') {
35645                         return new Date(v*1000);
35646                     }
35647                     if (df == 'time') {
35648                         return new Date(parseInt(v, 10));
35649                     }
35650                     return Ext.Date.parse(v, df);
35651                 }
35652                 
35653                 var parsed = Date.parse(v);
35654                 return parsed ? new Date(parsed) : null;
35655             },
35656             sortType: st.asDate,
35657             type: 'date'
35658         }
35659     });
35660     
35661     Ext.apply(Ext.data.Types, {
35662         /**
35663          * @type Object.
35664          * @property BOOLEAN
35665          * <p>This data type means that the raw data is converted into a boolean before it is placed into
35666          * a Record. The string "true" and the number 1 are converted to boolean <code>true</code>.</p>
35667          * <p>The synonym <code>BOOL</code> is equivalent.</p>
35668          */
35669         BOOLEAN: this.BOOL,
35670         
35671         /**
35672          * @type Object.
35673          * @property INTEGER
35674          * This data type means that the raw data is converted into an integer before it is placed into a Record.
35675          * <p>The synonym <code>INT</code> is equivalent.</p>
35676          */
35677         INTEGER: this.INT,
35678         
35679         /**
35680          * @type Object.
35681          * @property NUMBER
35682          * This data type means that the raw data is converted into a number before it is placed into a Record.
35683          * <p>The synonym <code>FLOAT</code> is equivalent.</p>
35684          */
35685         NUMBER: this.FLOAT    
35686     });
35687 });
35688
35689 /**
35690  * @author Ed Spencer
35691  * @class Ext.data.Field
35692  * @extends Object
35693  * 
35694  * <p>Fields are used to define what a Model is. They aren't instantiated directly - instead, when we create a class 
35695  * that extends {@link Ext.data.Model}, it will automatically create a Field instance for each field configured in a 
35696  * {@link Ext.data.Model Model}. For example, we might set up a model like this:</p>
35697  * 
35698 <pre><code>
35699 Ext.define('User', {
35700     extend: 'Ext.data.Model',
35701     fields: [
35702         'name', 'email',
35703         {name: 'age', type: 'int'},
35704         {name: 'gender', type: 'string', defaultValue: 'Unknown'}
35705     ]
35706 });
35707 </code></pre>
35708  * 
35709  * <p>Four fields will have been created for the User Model - name, email, age and gender. Note that we specified a
35710  * couple of different formats here; if we only pass in the string name of the field (as with name and email), the
35711  * field is set up with the 'auto' type. It's as if we'd done this instead:</p>
35712  * 
35713 <pre><code>
35714 Ext.define('User', {
35715     extend: 'Ext.data.Model',
35716     fields: [
35717         {name: 'name', type: 'auto'},
35718         {name: 'email', type: 'auto'},
35719         {name: 'age', type: 'int'},
35720         {name: 'gender', type: 'string', defaultValue: 'Unknown'}
35721     ]
35722 });
35723 </code></pre>
35724  * 
35725  * <p><u>Types and conversion</u></p>
35726  * 
35727  * <p>The {@link #type} is important - it's used to automatically convert data passed to the field into the correct
35728  * format. In our example above, the name and email fields used the 'auto' type and will just accept anything that is
35729  * passed into them. The 'age' field had an 'int' type however, so if we passed 25.4 this would be rounded to 25.</p>
35730  * 
35731  * <p>Sometimes a simple type isn't enough, or we want to perform some processing when we load a Field's data. We can
35732  * do this using a {@link #convert} function. Here, we're going to create a new field based on another:</p>
35733  * 
35734 <code><pre>
35735 Ext.define('User', {
35736     extend: 'Ext.data.Model',
35737     fields: [
35738         'name', 'email',
35739         {name: 'age', type: 'int'},
35740         {name: 'gender', type: 'string', defaultValue: 'Unknown'},
35741
35742         {
35743             name: 'firstName',
35744             convert: function(value, record) {
35745                 var fullName  = record.get('name'),
35746                     splits    = fullName.split(" "),
35747                     firstName = splits[0];
35748
35749                 return firstName;
35750             }
35751         }
35752     ]
35753 });
35754 </code></pre>
35755  * 
35756  * <p>Now when we create a new User, the firstName is populated automatically based on the name:</p>
35757  * 
35758 <code><pre>
35759 var ed = Ext.ModelManager.create({name: 'Ed Spencer'}, 'User');
35760
35761 console.log(ed.get('firstName')); //logs 'Ed', based on our convert function
35762 </code></pre>
35763  * 
35764  * <p>In fact, if we log out all of the data inside ed, we'll see this:</p>
35765  * 
35766 <code><pre>
35767 console.log(ed.data);
35768
35769 //outputs this:
35770 {
35771     age: 0,
35772     email: "",
35773     firstName: "Ed",
35774     gender: "Unknown",
35775     name: "Ed Spencer"
35776 }
35777 </code></pre>
35778  * 
35779  * <p>The age field has been given a default of zero because we made it an int type. As an auto field, email has
35780  * defaulted to an empty string. When we registered the User model we set gender's {@link #defaultValue} to 'Unknown'
35781  * so we see that now. Let's correct that and satisfy ourselves that the types work as we expect:</p>
35782  * 
35783 <code><pre>
35784 ed.set('gender', 'Male');
35785 ed.get('gender'); //returns 'Male'
35786
35787 ed.set('age', 25.4);
35788 ed.get('age'); //returns 25 - we wanted an int, not a float, so no decimal places allowed
35789 </code></pre>
35790  * 
35791  */
35792 Ext.define('Ext.data.Field', {
35793     requires: ['Ext.data.Types', 'Ext.data.SortTypes'],
35794     alias: 'data.field',
35795     
35796     constructor : function(config) {
35797         if (Ext.isString(config)) {
35798             config = {name: config};
35799         }
35800         Ext.apply(this, config);
35801         
35802         var types = Ext.data.Types,
35803             st = this.sortType,
35804             t;
35805
35806         if (this.type) {
35807             if (Ext.isString(this.type)) {
35808                 this.type = types[this.type.toUpperCase()] || types.AUTO;
35809             }
35810         } else {
35811             this.type = types.AUTO;
35812         }
35813
35814         // named sortTypes are supported, here we look them up
35815         if (Ext.isString(st)) {
35816             this.sortType = Ext.data.SortTypes[st];
35817         } else if(Ext.isEmpty(st)) {
35818             this.sortType = this.type.sortType;
35819         }
35820
35821         if (!this.convert) {
35822             this.convert = this.type.convert;
35823         }
35824     },
35825     
35826     /**
35827      * @cfg {String} name
35828      * The name by which the field is referenced within the Model. This is referenced by, for example,
35829      * the <code>dataIndex</code> property in column definition objects passed to {@link Ext.grid.property.HeaderContainer}.
35830      * <p>Note: In the simplest case, if no properties other than <code>name</code> are required, a field
35831      * definition may consist of just a String for the field name.</p>
35832      */
35833     
35834     /**
35835      * @cfg {Mixed} type
35836      * (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>
35837      * has not been specified. This may be specified as a string value. Possible values are
35838      * <div class="mdetail-params"><ul>
35839      * <li>auto (Default, implies no conversion)</li>
35840      * <li>string</li>
35841      * <li>int</li>
35842      * <li>float</li>
35843      * <li>boolean</li>
35844      * <li>date</li></ul></div>
35845      * <p>This may also be specified by referencing a member of the {@link Ext.data.Types} class.</p>
35846      * <p>Developers may create their own application-specific data types by defining new members of the
35847      * {@link Ext.data.Types} class.</p>
35848      */
35849     
35850     /**
35851      * @cfg {Function} convert
35852      * (Optional) A function which converts the value provided by the Reader into an object that will be stored
35853      * in the Model. It is passed the following parameters:<div class="mdetail-params"><ul>
35854      * <li><b>v</b> : Mixed<div class="sub-desc">The data value as read by the Reader, if undefined will use
35855      * the configured <code>{@link Ext.data.Field#defaultValue defaultValue}</code>.</div></li>
35856      * <li><b>rec</b> : Ext.data.Model<div class="sub-desc">The data object containing the Model as read so far by the 
35857      * Reader. Note that the Model may not be fully populated at this point as the fields are read in the order that 
35858      * they are defined in your {@link #fields} array.</div></li>
35859      * </ul></div>
35860      * <pre><code>
35861 // example of convert function
35862 function fullName(v, record){
35863     return record.name.last + ', ' + record.name.first;
35864 }
35865
35866 function location(v, record){
35867     return !record.city ? '' : (record.city + ', ' + record.state);
35868 }
35869
35870 Ext.define('Dude', {
35871     extend: 'Ext.data.Model',
35872     fields: [
35873         {name: 'fullname',  convert: fullName},
35874         {name: 'firstname', mapping: 'name.first'},
35875         {name: 'lastname',  mapping: 'name.last'},
35876         {name: 'city', defaultValue: 'homeless'},
35877         'state',
35878         {name: 'location',  convert: location}
35879     ]
35880 });
35881
35882 // create the data store
35883 var store = new Ext.data.Store({
35884     reader: {
35885         type: 'json',
35886         model: 'Dude',
35887         idProperty: 'key',
35888         root: 'daRoot',
35889         totalProperty: 'total'
35890     }
35891 });
35892
35893 var myData = [
35894     { key: 1,
35895       name: { first: 'Fat',    last:  'Albert' }
35896       // notice no city, state provided in data object
35897     },
35898     { key: 2,
35899       name: { first: 'Barney', last:  'Rubble' },
35900       city: 'Bedrock', state: 'Stoneridge'
35901     },
35902     { key: 3,
35903       name: { first: 'Cliff',  last:  'Claven' },
35904       city: 'Boston',  state: 'MA'
35905     }
35906 ];
35907      * </code></pre>
35908      */
35909     /**
35910      * @cfg {String} dateFormat
35911      * <p>(Optional) Used when converting received data into a Date when the {@link #type} is specified as <code>"date"</code>.</p>
35912      * <p>A format string for the {@link Ext.Date#parse Ext.Date.parse} function, or "timestamp" if the
35913      * value provided by the Reader is a UNIX timestamp, or "time" if the value provided by the Reader is a
35914      * javascript millisecond timestamp. See {@link Date}</p>
35915      */
35916     dateFormat: null,
35917     
35918     /**
35919      * @cfg {Boolean} useNull
35920      * <p>(Optional) Use when converting received data into a Number type (either int or float). If the value cannot be parsed,
35921      * null will be used if useNull is true, otherwise the value will be 0. Defaults to <tt>false</tt>
35922      */
35923     useNull: false,
35924     
35925     /**
35926      * @cfg {Mixed} defaultValue
35927      * (Optional) The default value used <b>when a Model is being created by a {@link Ext.data.reader.Reader Reader}</b>
35928      * when the item referenced by the <code>{@link Ext.data.Field#mapping mapping}</code> does not exist in the data
35929      * object (i.e. undefined). (defaults to "")
35930      */
35931     defaultValue: "",
35932     /**
35933      * @cfg {String/Number} mapping
35934      * <p>(Optional) A path expression for use by the {@link Ext.data.reader.Reader} implementation
35935      * that is creating the {@link Ext.data.Model Model} to extract the Field value from the data object.
35936      * If the path expression is the same as the field name, the mapping may be omitted.</p>
35937      * <p>The form of the mapping expression depends on the Reader being used.</p>
35938      * <div class="mdetail-params"><ul>
35939      * <li>{@link Ext.data.reader.Json}<div class="sub-desc">The mapping is a string containing the javascript
35940      * 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>
35941      * <li>{@link Ext.data.reader.Xml}<div class="sub-desc">The mapping is an {@link Ext.DomQuery} path to the data
35942      * item relative to the DOM element that represents the {@link Ext.data.reader.Xml#record record}. Defaults to the field name.</div></li>
35943      * <li>{@link Ext.data.reader.Array}<div class="sub-desc">The mapping is a number indicating the Array index
35944      * of the field's value. Defaults to the field specification's Array position.</div></li>
35945      * </ul></div>
35946      * <p>If a more complex value extraction strategy is required, then configure the Field with a {@link #convert}
35947      * function. This is passed the whole row object, and may interrogate it in whatever way is necessary in order to
35948      * return the desired data.</p>
35949      */
35950     mapping: null,
35951     /**
35952      * @cfg {Function} sortType
35953      * (Optional) A function which converts a Field's value to a comparable value in order to ensure
35954      * correct sort ordering. Predefined functions are provided in {@link Ext.data.SortTypes}. A custom
35955      * sort example:<pre><code>
35956 // current sort     after sort we want
35957 // +-+------+          +-+------+
35958 // |1|First |          |1|First |
35959 // |2|Last  |          |3|Second|
35960 // |3|Second|          |2|Last  |
35961 // +-+------+          +-+------+
35962
35963 sortType: function(value) {
35964    switch (value.toLowerCase()) // native toLowerCase():
35965    {
35966       case 'first': return 1;
35967       case 'second': return 2;
35968       default: return 3;
35969    }
35970 }
35971      * </code></pre>
35972      */
35973     sortType : null,
35974     /**
35975      * @cfg {String} sortDir
35976      * (Optional) Initial direction to sort (<code>"ASC"</code> or  <code>"DESC"</code>).  Defaults to
35977      * <code>"ASC"</code>.
35978      */
35979     sortDir : "ASC",
35980     /**
35981      * @cfg {Boolean} allowBlank
35982      * @private
35983      * (Optional) Used for validating a {@link Ext.data.Model model}, defaults to <code>true</code>.
35984      * An empty value here will cause {@link Ext.data.Model}.{@link Ext.data.Model#isValid isValid}
35985      * to evaluate to <code>false</code>.
35986      */
35987     allowBlank : true,
35988     
35989     /**
35990      * @cfg {Boolean} persist
35991      * False to exclude this field from the {@link Ext.data.Model#modified} fields in a model. This 
35992      * will also exclude the field from being written using a {@link Ext.data.writer.Writer}. This option
35993      * is useful when model fields are used to keep state on the client but do not need to be persisted
35994      * to the server. Defaults to <tt>true</tt>.
35995      */
35996     persist: true
35997 });
35998
35999 /**
36000  * @author Ed Spencer
36001  * @class Ext.data.reader.Reader
36002  * @extends Object
36003  * 
36004  * <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}
36005  * - usually in response to an AJAX request. This is normally handled transparently by passing some configuration to either the 
36006  * {@link Ext.data.Model Model} or the {@link Ext.data.Store Store} in question - see their documentation for further details.</p>
36007  * 
36008  * <p><u>Loading Nested Data</u></p>
36009  * 
36010  * <p>Readers have the ability to automatically load deeply-nested data objects based on the {@link Ext.data.Association associations}
36011  * configured on each Model. Below is an example demonstrating the flexibility of these associations in a fictional CRM system which
36012  * manages a User, their Orders, OrderItems and Products. First we'll define the models:
36013  * 
36014 <pre><code>
36015 Ext.define("User", {
36016     extend: 'Ext.data.Model',
36017     fields: [
36018         'id', 'name'
36019     ],
36020
36021     hasMany: {model: 'Order', name: 'orders'},
36022
36023     proxy: {
36024         type: 'rest',
36025         url : 'users.json',
36026         reader: {
36027             type: 'json',
36028             root: 'users'
36029         }
36030     }
36031 });
36032
36033 Ext.define("Order", {
36034     extend: 'Ext.data.Model',
36035     fields: [
36036         'id', 'total'
36037     ],
36038
36039     hasMany  : {model: 'OrderItem', name: 'orderItems', associationKey: 'order_items'},
36040     belongsTo: 'User'
36041 });
36042
36043 Ext.define("OrderItem", {
36044     extend: 'Ext.data.Model',
36045     fields: [
36046         'id', 'price', 'quantity', 'order_id', 'product_id'
36047     ],
36048
36049     belongsTo: ['Order', {model: 'Product', associationKey: 'product'}]
36050 });
36051
36052 Ext.define("Product", {
36053     extend: 'Ext.data.Model',
36054     fields: [
36055         'id', 'name'
36056     ],
36057
36058     hasMany: 'OrderItem'
36059 });
36060 </code></pre>
36061  * 
36062  * <p>This may be a lot to take in - basically a User has many Orders, each of which is composed of several OrderItems. Finally,
36063  * each OrderItem has a single Product. This allows us to consume data like this:</p>
36064  * 
36065 <pre><code>
36066 {
36067     "users": [
36068         {
36069             "id": 123,
36070             "name": "Ed",
36071             "orders": [
36072                 {
36073                     "id": 50,
36074                     "total": 100,
36075                     "order_items": [
36076                         {
36077                             "id"      : 20,
36078                             "price"   : 40,
36079                             "quantity": 2,
36080                             "product" : {
36081                                 "id": 1000,
36082                                 "name": "MacBook Pro"
36083                             }
36084                         },
36085                         {
36086                             "id"      : 21,
36087                             "price"   : 20,
36088                             "quantity": 3,
36089                             "product" : {
36090                                 "id": 1001,
36091                                 "name": "iPhone"
36092                             }
36093                         }
36094                     ]
36095                 }
36096             ]
36097         }
36098     ]
36099 }
36100 </code></pre>
36101  * 
36102  * <p>The JSON response is deeply nested - it returns all Users (in this case just 1 for simplicity's sake), all of the Orders
36103  * for each User (again just 1 in this case), all of the OrderItems for each Order (2 order items in this case), and finally
36104  * the Product associated with each OrderItem. Now we can read the data and use it as follows:
36105  * 
36106 <pre><code>
36107 var store = new Ext.data.Store({
36108     model: "User"
36109 });
36110
36111 store.load({
36112     callback: function() {
36113         //the user that was loaded
36114         var user = store.first();
36115
36116         console.log("Orders for " + user.get('name') + ":")
36117
36118         //iterate over the Orders for each User
36119         user.orders().each(function(order) {
36120             console.log("Order ID: " + order.getId() + ", which contains items:");
36121
36122             //iterate over the OrderItems for each Order
36123             order.orderItems().each(function(orderItem) {
36124                 //we know that the Product data is already loaded, so we can use the synchronous getProduct
36125                 //usually, we would use the asynchronous version (see {@link Ext.data.BelongsToAssociation})
36126                 var product = orderItem.getProduct();
36127
36128                 console.log(orderItem.get('quantity') + ' orders of ' + product.get('name'));
36129             });
36130         });
36131     }
36132 });
36133 </code></pre>
36134  * 
36135  * <p>Running the code above results in the following:</p>
36136  * 
36137 <pre><code>
36138 Orders for Ed:
36139 Order ID: 50, which contains items:
36140 2 orders of MacBook Pro
36141 3 orders of iPhone
36142 </code></pre>
36143  * 
36144  * @constructor
36145  * @param {Object} config Optional config object
36146  */
36147 Ext.define('Ext.data.reader.Reader', {
36148     requires: ['Ext.data.ResultSet'],
36149     alternateClassName: ['Ext.data.Reader', 'Ext.data.DataReader'],
36150     
36151     /**
36152      * @cfg {String} idProperty Name of the property within a row object
36153      * that contains a record identifier value.  Defaults to <tt>The id of the model</tt>.
36154      * If an idProperty is explicitly specified it will override that of the one specified
36155      * on the model
36156      */
36157
36158     /**
36159      * @cfg {String} totalProperty Name of the property from which to
36160      * retrieve the total number of records in the dataset. This is only needed
36161      * if the whole dataset is not passed in one go, but is being paged from
36162      * the remote server.  Defaults to <tt>total</tt>.
36163      */
36164     totalProperty: 'total',
36165
36166     /**
36167      * @cfg {String} successProperty Name of the property from which to
36168      * retrieve the success attribute. Defaults to <tt>success</tt>.  See
36169      * {@link Ext.data.proxy.Proxy}.{@link Ext.data.proxy.Proxy#exception exception}
36170      * for additional information.
36171      */
36172     successProperty: 'success',
36173
36174     /**
36175      * @cfg {String} root <b>Required</b>.  The name of the property
36176      * which contains the Array of row objects.  Defaults to <tt>undefined</tt>.
36177      * An exception will be thrown if the root property is undefined. The data
36178      * packet value for this property should be an empty array to clear the data
36179      * or show no data.
36180      */
36181     root: '',
36182     
36183     /**
36184      * @cfg {String} messageProperty The name of the property which contains a response message.
36185      * This property is optional.
36186      */
36187     
36188     /**
36189      * @cfg {Boolean} implicitIncludes True to automatically parse models nested within other models in a response
36190      * object. See the Ext.data.reader.Reader intro docs for full explanation. Defaults to true.
36191      */
36192     implicitIncludes: true,
36193     
36194     isReader: true,
36195     
36196     constructor: function(config) {
36197         var me = this;
36198         
36199         Ext.apply(me, config || {});
36200         me.fieldCount = 0;
36201         me.model = Ext.ModelManager.getModel(config.model);
36202         if (me.model) {
36203             me.buildExtractors();
36204         }
36205     },
36206
36207     /**
36208      * Sets a new model for the reader.
36209      * @private
36210      * @param {Object} model The model to set.
36211      * @param {Boolean} setOnProxy True to also set on the Proxy, if one is configured
36212      */
36213     setModel: function(model, setOnProxy) {
36214         var me = this;
36215         
36216         me.model = Ext.ModelManager.getModel(model);
36217         me.buildExtractors(true);
36218         
36219         if (setOnProxy && me.proxy) {
36220             me.proxy.setModel(me.model, true);
36221         }
36222     },
36223
36224     /**
36225      * Reads the given response object. This method normalizes the different types of response object that may be passed
36226      * to it, before handing off the reading of records to the {@link #readRecords} function.
36227      * @param {Object} response The response object. This may be either an XMLHttpRequest object or a plain JS object
36228      * @return {Ext.data.ResultSet} The parsed ResultSet object
36229      */
36230     read: function(response) {
36231         var data = response;
36232         
36233         if (response && response.responseText) {
36234             data = this.getResponseData(response);
36235         }
36236         
36237         if (data) {
36238             return this.readRecords(data);
36239         } else {
36240             return this.nullResultSet;
36241         }
36242     },
36243
36244     /**
36245      * Abstracts common functionality used by all Reader subclasses. Each subclass is expected to call
36246      * this function before running its own logic and returning the Ext.data.ResultSet instance. For most
36247      * Readers additional processing should not be needed.
36248      * @param {Mixed} data The raw data object
36249      * @return {Ext.data.ResultSet} A ResultSet object
36250      */
36251     readRecords: function(data) {
36252         var me  = this;
36253         
36254         /*
36255          * We check here whether the number of fields has changed since the last read.
36256          * This works around an issue when a Model is used for both a Tree and another
36257          * source, because the tree decorates the model with extra fields and it causes
36258          * issues because the readers aren't notified.
36259          */
36260         if (me.fieldCount !== me.getFields().length) {
36261             me.buildExtractors(true);
36262         }
36263         
36264         /**
36265          * The raw data object that was last passed to readRecords. Stored for further processing if needed
36266          * @property rawData
36267          * @type Mixed
36268          */
36269         me.rawData = data;
36270
36271         data = me.getData(data);
36272
36273         // If we pass an array as the data, we dont use getRoot on the data.
36274         // Instead the root equals to the data.
36275         var root    = Ext.isArray(data) ? data : me.getRoot(data),
36276             success = true,
36277             recordCount = 0,
36278             total, value, records, message;
36279             
36280         if (root) {
36281             total = root.length;
36282         }
36283
36284         if (me.totalProperty) {
36285             value = parseInt(me.getTotal(data), 10);
36286             if (!isNaN(value)) {
36287                 total = value;
36288             }
36289         }
36290
36291         if (me.successProperty) {
36292             value = me.getSuccess(data);
36293             if (value === false || value === 'false') {
36294                 success = false;
36295             }
36296         }
36297         
36298         if (me.messageProperty) {
36299             message = me.getMessage(data);
36300         }
36301         
36302         if (root) {
36303             records = me.extractData(root);
36304             recordCount = records.length;
36305         } else {
36306             recordCount = 0;
36307             records = [];
36308         }
36309
36310         return Ext.create('Ext.data.ResultSet', {
36311             total  : total || recordCount,
36312             count  : recordCount,
36313             records: records,
36314             success: success,
36315             message: message
36316         });
36317     },
36318
36319     /**
36320      * Returns extracted, type-cast rows of data.  Iterates to call #extractValues for each row
36321      * @param {Object[]/Object} data-root from server response
36322      * @private
36323      */
36324     extractData : function(root) {
36325         var me = this,
36326             values  = [],
36327             records = [],
36328             Model   = me.model,
36329             i       = 0,
36330             length  = root.length,
36331             idProp  = me.getIdProperty(),
36332             node, id, record;
36333             
36334         if (!root.length && Ext.isObject(root)) {
36335             root = [root];
36336             length = 1;
36337         }
36338
36339         for (; i < length; i++) {
36340             node   = root[i];
36341             values = me.extractValues(node);
36342             id     = me.getId(node);
36343
36344             
36345             record = new Model(values, id);
36346             record.raw = node;
36347             records.push(record);
36348                 
36349             if (me.implicitIncludes) {
36350                 me.readAssociated(record, node);
36351             }
36352         }
36353
36354         return records;
36355     },
36356     
36357     /**
36358      * @private
36359      * Loads a record's associations from the data object. This prepopulates hasMany and belongsTo associations
36360      * on the record provided.
36361      * @param {Ext.data.Model} record The record to load associations for
36362      * @param {Mixed} data The data object
36363      * @return {String} Return value description
36364      */
36365     readAssociated: function(record, data) {
36366         var associations = record.associations.items,
36367             i            = 0,
36368             length       = associations.length,
36369             association, associationData, proxy, reader;
36370         
36371         for (; i < length; i++) {
36372             association     = associations[i];
36373             associationData = this.getAssociatedDataRoot(data, association.associationKey || association.name);
36374             
36375             if (associationData) {
36376                 reader = association.getReader();
36377                 if (!reader) {
36378                     proxy = association.associatedModel.proxy;
36379                     // if the associated model has a Reader already, use that, otherwise attempt to create a sensible one
36380                     if (proxy) {
36381                         reader = proxy.getReader();
36382                     } else {
36383                         reader = new this.constructor({
36384                             model: association.associatedName
36385                         });
36386                     }
36387                 }
36388                 association.read(record, reader, associationData);
36389             }  
36390         }
36391     },
36392     
36393     /**
36394      * @private
36395      * Used internally by {@link #readAssociated}. Given a data object (which could be json, xml etc) for a specific
36396      * record, this should return the relevant part of that data for the given association name. This is only really
36397      * needed to support the XML Reader, which has to do a query to get the associated data object
36398      * @param {Mixed} data The raw data object
36399      * @param {String} associationName The name of the association to get data for (uses associationKey if present)
36400      * @return {Mixed} The root
36401      */
36402     getAssociatedDataRoot: function(data, associationName) {
36403         return data[associationName];
36404     },
36405     
36406     getFields: function() {
36407         return this.model.prototype.fields.items;
36408     },
36409
36410     /**
36411      * @private
36412      * Given an object representing a single model instance's data, iterates over the model's fields and
36413      * builds an object with the value for each field.
36414      * @param {Object} data The data object to convert
36415      * @return {Object} Data object suitable for use with a model constructor
36416      */
36417     extractValues: function(data) {
36418         var fields = this.getFields(),
36419             i      = 0,
36420             length = fields.length,
36421             output = {},
36422             field, value;
36423
36424         for (; i < length; i++) {
36425             field = fields[i];
36426             value = this.extractorFunctions[i](data);
36427
36428             output[field.name] = value;
36429         }
36430
36431         return output;
36432     },
36433
36434     /**
36435      * @private
36436      * By default this function just returns what is passed to it. It can be overridden in a subclass
36437      * to return something else. See XmlReader for an example.
36438      * @param {Object} data The data object
36439      * @return {Object} The normalized data object
36440      */
36441     getData: function(data) {
36442         return data;
36443     },
36444
36445     /**
36446      * @private
36447      * This will usually need to be implemented in a subclass. Given a generic data object (the type depends on the type
36448      * of data we are reading), this function should return the object as configured by the Reader's 'root' meta data config.
36449      * See XmlReader's getRoot implementation for an example. By default the same data object will simply be returned.
36450      * @param {Mixed} data The data object
36451      * @return {Mixed} The same data object
36452      */
36453     getRoot: function(data) {
36454         return data;
36455     },
36456
36457     /**
36458      * 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
36459      * @param {Object} response The responce object
36460      * @return {Object} The useful data from the response
36461      */
36462     getResponseData: function(response) {
36463         Ext.Error.raise("getResponseData must be implemented in the Ext.data.reader.Reader subclass");
36464     },
36465
36466     /**
36467      * @private
36468      * Reconfigures the meta data tied to this Reader
36469      */
36470     onMetaChange : function(meta) {
36471         var fields = meta.fields,
36472             newModel;
36473         
36474         Ext.apply(this, meta);
36475         
36476         if (fields) {
36477             newModel = Ext.define("Ext.data.reader.Json-Model" + Ext.id(), {
36478                 extend: 'Ext.data.Model',
36479                 fields: fields
36480             });
36481             this.setModel(newModel, true);
36482         } else {
36483             this.buildExtractors(true);
36484         }
36485     },
36486     
36487     /**
36488      * Get the idProperty to use for extracting data
36489      * @private
36490      * @return {String} The id property
36491      */
36492     getIdProperty: function(){
36493         var prop = this.idProperty;
36494         if (Ext.isEmpty(prop)) {
36495             prop = this.model.prototype.idProperty;
36496         }
36497         return prop;
36498     },
36499
36500     /**
36501      * @private
36502      * This builds optimized functions for retrieving record data and meta data from an object.
36503      * Subclasses may need to implement their own getRoot function.
36504      * @param {Boolean} force True to automatically remove existing extractor functions first (defaults to false)
36505      */
36506     buildExtractors: function(force) {
36507         var me          = this,
36508             idProp      = me.getIdProperty(),
36509             totalProp   = me.totalProperty,
36510             successProp = me.successProperty,
36511             messageProp = me.messageProperty,
36512             accessor;
36513             
36514         if (force === true) {
36515             delete me.extractorFunctions;
36516         }
36517         
36518         if (me.extractorFunctions) {
36519             return;
36520         }   
36521
36522         //build the extractors for all the meta data
36523         if (totalProp) {
36524             me.getTotal = me.createAccessor(totalProp);
36525         }
36526
36527         if (successProp) {
36528             me.getSuccess = me.createAccessor(successProp);
36529         }
36530
36531         if (messageProp) {
36532             me.getMessage = me.createAccessor(messageProp);
36533         }
36534
36535         if (idProp) {
36536             accessor = me.createAccessor(idProp);
36537
36538             me.getId = function(record) {
36539                 var id = accessor.call(me, record);
36540                 return (id === undefined || id === '') ? null : id;
36541             };
36542         } else {
36543             me.getId = function() {
36544                 return null;
36545             };
36546         }
36547         me.buildFieldExtractors();
36548     },
36549
36550     /**
36551      * @private
36552      */
36553     buildFieldExtractors: function() {
36554         //now build the extractors for all the fields
36555         var me = this,
36556             fields = me.getFields(),
36557             ln = fields.length,
36558             i  = 0,
36559             extractorFunctions = [],
36560             field, map;
36561
36562         for (; i < ln; i++) {
36563             field = fields[i];
36564             map   = (field.mapping !== undefined && field.mapping !== null) ? field.mapping : field.name;
36565
36566             extractorFunctions.push(me.createAccessor(map));
36567         }
36568         me.fieldCount = ln;
36569
36570         me.extractorFunctions = extractorFunctions;
36571     }
36572 }, function() {
36573     Ext.apply(this, {
36574         // Private. Empty ResultSet to return when response is falsy (null|undefined|empty string)
36575         nullResultSet: Ext.create('Ext.data.ResultSet', {
36576             total  : 0,
36577             count  : 0,
36578             records: [],
36579             success: true
36580         })
36581     });
36582 });
36583 /**
36584  * @author Ed Spencer
36585  * @class Ext.data.reader.Json
36586  * @extends Ext.data.reader.Reader
36587  * 
36588  * <p>The JSON Reader is used by a Proxy to read a server response that is sent back in JSON format. This usually
36589  * happens as a result of loading a Store - for example we might create something like this:</p>
36590  * 
36591 <pre><code>
36592 Ext.define('User', {
36593     extend: 'Ext.data.Model',
36594     fields: ['id', 'name', 'email']
36595 });
36596
36597 var store = new Ext.data.Store({
36598     model: 'User',
36599     proxy: {
36600         type: 'ajax',
36601         url : 'users.json',
36602         reader: {
36603             type: 'json'
36604         }
36605     }
36606 });
36607 </code></pre>
36608  * 
36609  * <p>The example above creates a 'User' model. Models are explained in the {@link Ext.data.Model Model} docs if you're
36610  * not already familiar with them.</p>
36611  * 
36612  * <p>We created the simplest type of JSON Reader possible by simply telling our {@link Ext.data.Store Store}'s 
36613  * {@link Ext.data.proxy.Proxy Proxy} that we want a JSON Reader. The Store automatically passes the configured model to the
36614  * Store, so it is as if we passed this instead:
36615  * 
36616 <pre><code>
36617 reader: {
36618     type : 'json',
36619     model: 'User'
36620 }
36621 </code></pre>
36622  * 
36623  * <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>
36624  * 
36625 <pre><code>
36626 [
36627     {
36628         "id": 1,
36629         "name": "Ed Spencer",
36630         "email": "ed@sencha.com"
36631     },
36632     {
36633         "id": 2,
36634         "name": "Abe Elias",
36635         "email": "abe@sencha.com"
36636     }
36637 ]
36638 </code></pre>
36639  * 
36640  * <p><u>Reading other JSON formats</u></p>
36641  * 
36642  * <p>If you already have your JSON format defined and it doesn't look quite like what we have above, you can usually
36643  * pass JsonReader a couple of configuration options to make it parse your format. For example, we can use the 
36644  * {@link #root} configuration to parse data that comes back like this:</p>
36645  * 
36646 <pre><code>
36647 {
36648     "users": [
36649        {
36650            "id": 1,
36651            "name": "Ed Spencer",
36652            "email": "ed@sencha.com"
36653        },
36654        {
36655            "id": 2,
36656            "name": "Abe Elias",
36657            "email": "abe@sencha.com"
36658        }
36659     ]
36660 }
36661 </code></pre>
36662  * 
36663  * <p>To parse this we just pass in a {@link #root} configuration that matches the 'users' above:</p>
36664  * 
36665 <pre><code>
36666 reader: {
36667     type: 'json',
36668     root: 'users'
36669 }
36670 </code></pre>
36671  * 
36672  * <p>Sometimes the JSON structure is even more complicated. Document databases like CouchDB often provide metadata
36673  * around each record inside a nested structure like this:</p>
36674  * 
36675 <pre><code>
36676 {
36677     "total": 122,
36678     "offset": 0,
36679     "users": [
36680         {
36681             "id": "ed-spencer-1",
36682             "value": 1,
36683             "user": {
36684                 "id": 1,
36685                 "name": "Ed Spencer",
36686                 "email": "ed@sencha.com"
36687             }
36688         }
36689     ]
36690 }
36691 </code></pre>
36692  * 
36693  * <p>In the case above the record data is nested an additional level inside the "users" array as each "user" item has
36694  * additional metadata surrounding it ('id' and 'value' in this case). To parse data out of each "user" item in the 
36695  * JSON above we need to specify the {@link #record} configuration like this:</p>
36696  * 
36697 <pre><code>
36698 reader: {
36699     type  : 'json',
36700     root  : 'users',
36701     record: 'user'
36702 }
36703 </code></pre>
36704  * 
36705  * <p><u>Response metadata</u></p>
36706  * 
36707  * <p>The server can return additional data in its response, such as the {@link #totalProperty total number of records} 
36708  * and the {@link #successProperty success status of the response}. These are typically included in the JSON response
36709  * like this:</p>
36710  * 
36711 <pre><code>
36712 {
36713     "total": 100,
36714     "success": true,
36715     "users": [
36716         {
36717             "id": 1,
36718             "name": "Ed Spencer",
36719             "email": "ed@sencha.com"
36720         }
36721     ]
36722 }
36723 </code></pre>
36724  * 
36725  * <p>If these properties are present in the JSON response they can be parsed out by the JsonReader and used by the
36726  * Store that loaded it. We can set up the names of these properties by specifying a final pair of configuration 
36727  * options:</p>
36728  * 
36729 <pre><code>
36730 reader: {
36731     type : 'json',
36732     root : 'users',
36733     totalProperty  : 'total',
36734     successProperty: 'success'
36735 }
36736 </code></pre>
36737  * 
36738  * <p>These final options are not necessary to make the Reader work, but can be useful when the server needs to report
36739  * an error or if it needs to indicate that there is a lot of data available of which only a subset is currently being
36740  * returned.</p>
36741  */
36742 Ext.define('Ext.data.reader.Json', {
36743     extend: 'Ext.data.reader.Reader',
36744     alternateClassName: 'Ext.data.JsonReader',
36745     alias : 'reader.json',
36746     
36747     root: '',
36748     
36749     /**
36750      * @cfg {String} record The optional location within the JSON response that the record data itself can be found at.
36751      * See the JsonReader intro docs for more details. This is not often needed and defaults to undefined.
36752      */
36753     
36754     /**
36755      * @cfg {Boolean} useSimpleAccessors True to ensure that field names/mappings are treated as literals when
36756      * reading values. Defalts to <tt>false</tt>.
36757      * For example, by default, using the mapping "foo.bar.baz" will try and read a property foo from the root, then a property bar
36758      * from foo, then a property baz from bar. Setting the simple accessors to true will read the property with the name 
36759      * "foo.bar.baz" direct from the root object.
36760      */
36761     useSimpleAccessors: false,
36762     
36763     /**
36764      * Reads a JSON object and returns a ResultSet. Uses the internal getTotal and getSuccess extractors to
36765      * retrieve meta data from the response, and extractData to turn the JSON data into model instances.
36766      * @param {Object} data The raw JSON data
36767      * @return {Ext.data.ResultSet} A ResultSet containing model instances and meta data about the results
36768      */
36769     readRecords: function(data) {
36770         //this has to be before the call to super because we use the meta data in the superclass readRecords
36771         if (data.metaData) {
36772             this.onMetaChange(data.metaData);
36773         }
36774
36775         /**
36776          * DEPRECATED - will be removed in Ext JS 5.0. This is just a copy of this.rawData - use that instead
36777          * @property jsonData
36778          * @type Mixed
36779          */
36780         this.jsonData = data;
36781         return this.callParent([data]);
36782     },
36783
36784     //inherit docs
36785     getResponseData: function(response) {
36786         try {
36787             var data = Ext.decode(response.responseText);
36788         }
36789         catch (ex) {
36790             Ext.Error.raise({
36791                 response: response,
36792                 json: response.responseText,
36793                 parseError: ex,
36794                 msg: 'Unable to parse the JSON returned by the server: ' + ex.toString()
36795             });
36796         }
36797         if (!data) {
36798             Ext.Error.raise('JSON object not found');
36799         }
36800
36801         return data;
36802     },
36803
36804     //inherit docs
36805     buildExtractors : function() {
36806         var me = this;
36807         
36808         me.callParent(arguments);
36809
36810         if (me.root) {
36811             me.getRoot = me.createAccessor(me.root);
36812         } else {
36813             me.getRoot = function(root) {
36814                 return root;
36815             };
36816         }
36817     },
36818     
36819     /**
36820      * @private
36821      * We're just preparing the data for the superclass by pulling out the record objects we want. If a {@link #record}
36822      * was specified we have to pull those out of the larger JSON object, which is most of what this function is doing
36823      * @param {Object} root The JSON root node
36824      * @return {Array} The records
36825      */
36826     extractData: function(root) {
36827         var recordName = this.record,
36828             data = [],
36829             length, i;
36830         
36831         if (recordName) {
36832             length = root.length;
36833             
36834             for (i = 0; i < length; i++) {
36835                 data[i] = root[i][recordName];
36836             }
36837         } else {
36838             data = root;
36839         }
36840         return this.callParent([data]);
36841     },
36842
36843     /**
36844      * @private
36845      * Returns an accessor function for the given property string. Gives support for properties such as the following:
36846      * 'someProperty'
36847      * 'some.property'
36848      * 'some["property"]'
36849      * This is used by buildExtractors to create optimized extractor functions when casting raw data into model instances.
36850      */
36851     createAccessor: function() {
36852         var re = /[\[\.]/;
36853         
36854         return function(expr) {
36855             if (Ext.isEmpty(expr)) {
36856                 return Ext.emptyFn;
36857             }
36858             if (Ext.isFunction(expr)) {
36859                 return expr;
36860             }
36861             if (this.useSimpleAccessors !== true) {
36862                 var i = String(expr).search(re);
36863                 if (i >= 0) {
36864                     return Ext.functionFactory('obj', 'return obj' + (i > 0 ? '.' : '') + expr);
36865                 }
36866             }
36867             return function(obj) {
36868                 return obj[expr];
36869             };
36870         };
36871     }()
36872 });
36873 /**
36874  * @class Ext.data.writer.Json
36875  * @extends Ext.data.writer.Writer
36876  * @ignore
36877  */
36878 Ext.define('Ext.data.writer.Json', {
36879     extend: 'Ext.data.writer.Writer',
36880     alternateClassName: 'Ext.data.JsonWriter',
36881     alias: 'writer.json',
36882     
36883     /**
36884      * @cfg {String} root The key under which the records in this Writer will be placed. Defaults to <tt>undefined</tt>.
36885      * Example generated request, using root: 'records':
36886 <pre><code>
36887 {'records': [{name: 'my record'}, {name: 'another record'}]}
36888 </code></pre>
36889      */
36890     root: undefined,
36891     
36892     /**
36893      * @cfg {Boolean} encode True to use Ext.encode() on the data before sending. Defaults to <tt>false</tt>.
36894      * The encode option should only be set to true when a {@link #root} is defined, because the values will be
36895      * sent as part of the request parameters as opposed to a raw post. The root will be the name of the parameter
36896      * sent to the server.
36897      */
36898     encode: false,
36899     
36900     /**
36901      * @cfg {Boolean} allowSingle False to ensure that records are always wrapped in an array, even if there is only
36902      * one record being sent. When there is more than one record, they will always be encoded into an array.
36903      * Defaults to <tt>true</tt>. Example:
36904      * <pre><code>
36905 // with allowSingle: true
36906 "root": {
36907     "first": "Mark",
36908     "last": "Corrigan"
36909 }
36910
36911 // with allowSingle: false
36912 "root": [{
36913     "first": "Mark",
36914     "last": "Corrigan"
36915 }]
36916      * </code></pre>
36917      */
36918     allowSingle: true,
36919     
36920     //inherit docs
36921     writeRecords: function(request, data) {
36922         var root = this.root;
36923         
36924         if (this.allowSingle && data.length == 1) {
36925             // convert to single object format
36926             data = data[0];
36927         }
36928         
36929         if (this.encode) {
36930             if (root) {
36931                 // sending as a param, need to encode
36932                 request.params[root] = Ext.encode(data);
36933             } else {
36934                 Ext.Error.raise('Must specify a root when using encode');
36935             }
36936         } else {
36937             // send as jsonData
36938             request.jsonData = request.jsonData || {};
36939             if (root) {
36940                 request.jsonData[root] = data;
36941             } else {
36942                 request.jsonData = data;
36943             }
36944         }
36945         return request;
36946     }
36947 });
36948
36949 /**
36950  * @author Ed Spencer
36951  * @class Ext.data.proxy.Proxy
36952  * 
36953  * <p>Proxies are used by {@link Ext.data.Store Stores} to handle the loading and saving of {@link Ext.data.Model Model} data.
36954  * Usually developers will not need to create or interact with proxies directly.</p>
36955  * <p><u>Types of Proxy</u></p>
36956  * 
36957  * <p>There are two main types of Proxy - {@link Ext.data.proxy.Client Client} and {@link Ext.data.proxy.Server Server}. The Client proxies
36958  * save their data locally and include the following subclasses:</p>
36959  * 
36960  * <ul style="list-style-type: disc; padding-left: 25px">
36961  * <li>{@link Ext.data.proxy.LocalStorage LocalStorageProxy} - saves its data to localStorage if the browser supports it</li>
36962  * <li>{@link Ext.data.proxy.SessionStorage SessionStorageProxy} - saves its data to sessionStorage if the browsers supports it</li>
36963  * <li>{@link Ext.data.proxy.Memory MemoryProxy} - holds data in memory only, any data is lost when the page is refreshed</li>
36964  * </ul>
36965  * 
36966  * <p>The Server proxies save their data by sending requests to some remote server. These proxies include:</p>
36967  * 
36968  * <ul style="list-style-type: disc; padding-left: 25px">
36969  * <li>{@link Ext.data.proxy.Ajax Ajax} - sends requests to a server on the same domain</li>
36970  * <li>{@link Ext.data.proxy.JsonP JsonP} - uses JSON-P to send requests to a server on a different domain</li>
36971  * <li>{@link Ext.data.proxy.Direct Direct} - uses {@link Ext.direct} to send requests</li>
36972  * </ul>
36973  * 
36974  * <p>Proxies operate on the principle that all operations performed are either Create, Read, Update or Delete. These four operations 
36975  * are mapped to the methods {@link #create}, {@link #read}, {@link #update} and {@link #destroy} respectively. Each Proxy subclass 
36976  * implements these functions.</p>
36977  * 
36978  * <p>The CRUD methods each expect an {@link Ext.data.Operation Operation} object as the sole argument. The Operation encapsulates 
36979  * information about the action the Store wishes to perform, the {@link Ext.data.Model model} instances that are to be modified, etc.
36980  * See the {@link Ext.data.Operation Operation} documentation for more details. Each CRUD method also accepts a callback function to be 
36981  * called asynchronously on completion.</p>
36982  * 
36983  * <p>Proxies also support batching of Operations via a {@link Ext.data.Batch batch} object, invoked by the {@link #batch} method.</p>
36984  * 
36985  * @constructor
36986  * Creates the Proxy
36987  * @param {Object} config Optional config object
36988  */
36989 Ext.define('Ext.data.proxy.Proxy', {
36990     alias: 'proxy.proxy',
36991     alternateClassName: ['Ext.data.DataProxy', 'Ext.data.Proxy'],
36992     requires: [
36993         'Ext.data.reader.Json',
36994         'Ext.data.writer.Json'
36995     ],
36996     uses: [
36997         'Ext.data.Batch', 
36998         'Ext.data.Operation', 
36999         'Ext.data.Model'
37000     ],
37001     mixins: {
37002         observable: 'Ext.util.Observable'
37003     },
37004     
37005     /**
37006      * @cfg {String} batchOrder
37007      * Comma-separated ordering 'create', 'update' and 'destroy' actions when batching. Override this
37008      * to set a different order for the batched CRUD actions to be executed in. Defaults to 'create,update,destroy'
37009      */
37010     batchOrder: 'create,update,destroy',
37011     
37012     /**
37013      * @cfg {Boolean} batchActions True to batch actions of a particular type when synchronizing the store.
37014      * Defaults to <tt>true</tt>.
37015      */
37016     batchActions: true,
37017     
37018     /**
37019      * @cfg {String} defaultReaderType The default registered reader type. Defaults to 'json'
37020      * @private
37021      */
37022     defaultReaderType: 'json',
37023     
37024     /**
37025      * @cfg {String} defaultWriterType The default registered writer type. Defaults to 'json'
37026      * @private
37027      */
37028     defaultWriterType: 'json',
37029     
37030     /**
37031      * @cfg {String/Ext.data.Model} model The name of the Model to tie to this Proxy. Can be either the string name of
37032      * the Model, or a reference to the Model constructor. Required.
37033      */
37034     
37035     isProxy: true,
37036     
37037     constructor: function(config) {
37038         config = config || {};
37039         
37040         if (config.model === undefined) {
37041             delete config.model;
37042         }
37043
37044         this.mixins.observable.constructor.call(this, config);
37045         
37046         if (this.model !== undefined && !(this.model instanceof Ext.data.Model)) {
37047             this.setModel(this.model);
37048         }
37049     },
37050     
37051     /**
37052      * Sets the model associated with this proxy. This will only usually be called by a Store
37053      * @param {String|Ext.data.Model} model The new model. Can be either the model name string,
37054      * or a reference to the model's constructor
37055      * @param {Boolean} setOnStore Sets the new model on the associated Store, if one is present
37056      */
37057     setModel: function(model, setOnStore) {
37058         this.model = Ext.ModelManager.getModel(model);
37059         
37060         var reader = this.reader,
37061             writer = this.writer;
37062         
37063         this.setReader(reader);
37064         this.setWriter(writer);
37065         
37066         if (setOnStore && this.store) {
37067             this.store.setModel(this.model);
37068         }
37069     },
37070     
37071     /**
37072      * Returns the model attached to this Proxy
37073      * @return {Ext.data.Model} The model
37074      */
37075     getModel: function() {
37076         return this.model;
37077     },
37078     
37079     /**
37080      * Sets the Proxy's Reader by string, config object or Reader instance
37081      * @param {String|Object|Ext.data.reader.Reader} reader The new Reader, which can be either a type string, a configuration object
37082      * or an Ext.data.reader.Reader instance
37083      * @return {Ext.data.reader.Reader} The attached Reader object
37084      */
37085     setReader: function(reader) {
37086         var me = this;
37087         
37088         if (reader === undefined || typeof reader == 'string') {
37089             reader = {
37090                 type: reader
37091             };
37092         }
37093
37094         if (reader.isReader) {
37095             reader.setModel(me.model);
37096         } else {
37097             Ext.applyIf(reader, {
37098                 proxy: me,
37099                 model: me.model,
37100                 type : me.defaultReaderType
37101             });
37102
37103             reader = Ext.createByAlias('reader.' + reader.type, reader);
37104         }
37105         
37106         me.reader = reader;
37107         return me.reader;
37108     },
37109     
37110     /**
37111      * Returns the reader currently attached to this proxy instance
37112      * @return {Ext.data.reader.Reader} The Reader instance
37113      */
37114     getReader: function() {
37115         return this.reader;
37116     },
37117     
37118     /**
37119      * Sets the Proxy's Writer by string, config object or Writer instance
37120      * @param {String|Object|Ext.data.writer.Writer} writer The new Writer, which can be either a type string, a configuration object
37121      * or an Ext.data.writer.Writer instance
37122      * @return {Ext.data.writer.Writer} The attached Writer object
37123      */
37124     setWriter: function(writer) {
37125         if (writer === undefined || typeof writer == 'string') {
37126             writer = {
37127                 type: writer
37128             };
37129         }
37130
37131         if (!(writer instanceof Ext.data.writer.Writer)) {
37132             Ext.applyIf(writer, {
37133                 model: this.model,
37134                 type : this.defaultWriterType
37135             });
37136
37137             writer = Ext.createByAlias('writer.' + writer.type, writer);
37138         }
37139         
37140         this.writer = writer;
37141         
37142         return this.writer;
37143     },
37144     
37145     /**
37146      * Returns the writer currently attached to this proxy instance
37147      * @return {Ext.data.writer.Writer} The Writer instance
37148      */
37149     getWriter: function() {
37150         return this.writer;
37151     },
37152     
37153     /**
37154      * Performs the given create operation.
37155      * @param {Ext.data.Operation} operation The Operation to perform
37156      * @param {Function} callback Callback function to be called when the Operation has completed (whether successful or not)
37157      * @param {Object} scope Scope to execute the callback function in
37158      */
37159     create: Ext.emptyFn,
37160     
37161     /**
37162      * Performs the given read operation.
37163      * @param {Ext.data.Operation} operation The Operation to perform
37164      * @param {Function} callback Callback function to be called when the Operation has completed (whether successful or not)
37165      * @param {Object} scope Scope to execute the callback function in
37166      */
37167     read: Ext.emptyFn,
37168     
37169     /**
37170      * Performs the given update operation.
37171      * @param {Ext.data.Operation} operation The Operation to perform
37172      * @param {Function} callback Callback function to be called when the Operation has completed (whether successful or not)
37173      * @param {Object} scope Scope to execute the callback function in
37174      */
37175     update: Ext.emptyFn,
37176     
37177     /**
37178      * Performs the given destroy operation.
37179      * @param {Ext.data.Operation} operation The Operation to perform
37180      * @param {Function} callback Callback function to be called when the Operation has completed (whether successful or not)
37181      * @param {Object} scope Scope to execute the callback function in
37182      */
37183     destroy: Ext.emptyFn,
37184     
37185     /**
37186      * Performs a batch of {@link Ext.data.Operation Operations}, in the order specified by {@link #batchOrder}. Used internally by
37187      * {@link Ext.data.Store}'s {@link Ext.data.Store#sync sync} method. Example usage:
37188      * <pre><code>
37189      * myProxy.batch({
37190      *     create : [myModel1, myModel2],
37191      *     update : [myModel3],
37192      *     destroy: [myModel4, myModel5]
37193      * });
37194      * </code></pre>
37195      * Where the myModel* above are {@link Ext.data.Model Model} instances - in this case 1 and 2 are new instances and have not been 
37196      * 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.
37197      * @param {Object} operations Object containing the Model instances to act upon, keyed by action name
37198      * @param {Object} listeners Optional listeners object passed straight through to the Batch - see {@link Ext.data.Batch}
37199      * @return {Ext.data.Batch} The newly created Ext.data.Batch object
37200      */
37201     batch: function(operations, listeners) {
37202         var me = this,
37203             batch = Ext.create('Ext.data.Batch', {
37204                 proxy: me,
37205                 listeners: listeners || {}
37206             }),
37207             useBatch = me.batchActions, 
37208             records;
37209         
37210         Ext.each(me.batchOrder.split(','), function(action) {
37211             records = operations[action];
37212             if (records) {
37213                 if (useBatch) {
37214                     batch.add(Ext.create('Ext.data.Operation', {
37215                         action: action,
37216                         records: records
37217                     }));
37218                 } else {
37219                     Ext.each(records, function(record){
37220                         batch.add(Ext.create('Ext.data.Operation', {
37221                             action : action, 
37222                             records: [record]
37223                         }));
37224                     });
37225                 }
37226             }
37227         }, me);
37228         
37229         batch.start();
37230         return batch;
37231     }
37232 }, function() {
37233     // Ext.data.proxy.ProxyMgr.registerType('proxy', this);
37234     
37235     //backwards compatibility
37236     Ext.data.DataProxy = this;
37237     // Ext.deprecate('platform', '2.0', function() {
37238     //     Ext.data.DataProxy = this;
37239     // }, this);
37240 });
37241
37242 /**
37243  * @author Ed Spencer
37244  * @class Ext.data.proxy.Server
37245  * @extends Ext.data.proxy.Proxy
37246  * 
37247  * <p>ServerProxy is a superclass of {@link Ext.data.proxy.JsonP JsonPProxy} and {@link Ext.data.proxy.Ajax AjaxProxy},
37248  * and would not usually be used directly.</p>
37249  * 
37250  * <p>ServerProxy should ideally be named HttpProxy as it is a superclass for all HTTP proxies - for Ext JS 4.x it has been 
37251  * called ServerProxy to enable any 3.x applications that reference the HttpProxy to continue to work (HttpProxy is now an 
37252  * alias of AjaxProxy).</p>
37253  */
37254 Ext.define('Ext.data.proxy.Server', {
37255     extend: 'Ext.data.proxy.Proxy',
37256     alias : 'proxy.server',
37257     alternateClassName: 'Ext.data.ServerProxy',
37258     uses  : ['Ext.data.Request'],
37259     
37260     /**
37261      * @cfg {String} url The URL from which to request the data object.
37262      */
37263     
37264     /**
37265      * @cfg {Object/String/Ext.data.reader.Reader} reader The Ext.data.reader.Reader to use to decode the server's response. This can
37266      * either be a Reader instance, a config object or just a valid Reader type name (e.g. 'json', 'xml').
37267      */
37268     
37269     /**
37270      * @cfg {Object/String/Ext.data.writer.Writer} writer The Ext.data.writer.Writer to use to encode any request sent to the server.
37271      * This can either be a Writer instance, a config object or just a valid Writer type name (e.g. 'json', 'xml').
37272      */
37273     
37274     /**
37275      * @cfg {String} pageParam The name of the 'page' parameter to send in a request. Defaults to 'page'. Set this to
37276      * undefined if you don't want to send a page parameter
37277      */
37278     pageParam: 'page',
37279     
37280     /**
37281      * @cfg {String} startParam The name of the 'start' parameter to send in a request. Defaults to 'start'. Set this
37282      * to undefined if you don't want to send a start parameter
37283      */
37284     startParam: 'start',
37285
37286     /**
37287      * @cfg {String} limitParam The name of the 'limit' parameter to send in a request. Defaults to 'limit'. Set this
37288      * to undefined if you don't want to send a limit parameter
37289      */
37290     limitParam: 'limit',
37291     
37292     /**
37293      * @cfg {String} groupParam The name of the 'group' parameter to send in a request. Defaults to 'group'. Set this
37294      * to undefined if you don't want to send a group parameter
37295      */
37296     groupParam: 'group',
37297     
37298     /**
37299      * @cfg {String} sortParam The name of the 'sort' parameter to send in a request. Defaults to 'sort'. Set this
37300      * to undefined if you don't want to send a sort parameter
37301      */
37302     sortParam: 'sort',
37303     
37304     /**
37305      * @cfg {String} filterParam The name of the 'filter' parameter to send in a request. Defaults to 'filter'. Set 
37306      * this to undefined if you don't want to send a filter parameter
37307      */
37308     filterParam: 'filter',
37309     
37310     /**
37311      * @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>
37312      * Defaults to 'dir'.
37313      */
37314     directionParam: 'dir',
37315     
37316     /**
37317      * @cfg {Boolean} simpleSortMode Enabling simpleSortMode in conjunction with remoteSort will only send one sort property and a direction when a remote sort is requested.
37318      * The directionParam and sortParam will be sent with the property name and either 'ASC' or 'DESC'
37319      */
37320     simpleSortMode: false,
37321     
37322     /**
37323      * @cfg {Boolean} noCache (optional) Defaults to true. Disable caching by adding a unique parameter
37324      * name to the request.
37325      */
37326     noCache : true,
37327     
37328     /**
37329      * @cfg {String} cacheString The name of the cache param added to the url when using noCache (defaults to "_dc")
37330      */
37331     cacheString: "_dc",
37332     
37333     /**
37334      * @cfg {Number} timeout (optional) The number of milliseconds to wait for a response. Defaults to 30 seconds.
37335      */
37336     timeout : 30000,
37337     
37338     /**
37339      * @cfg {Object} api
37340      * Specific urls to call on CRUD action methods "read", "create", "update" and "destroy".
37341      * Defaults to:<pre><code>
37342 api: {
37343     read    : undefined,
37344     create  : undefined,
37345     update  : undefined,
37346     destroy : undefined
37347 }
37348      * </code></pre>
37349      * <p>The url is built based upon the action being executed <tt>[load|create|save|destroy]</tt>
37350      * using the commensurate <tt>{@link #api}</tt> property, or if undefined default to the
37351      * configured {@link Ext.data.Store}.{@link Ext.data.proxy.Server#url url}.</p><br>
37352      * <p>For example:</p>
37353      * <pre><code>
37354 api: {
37355     load :    '/controller/load',
37356     create :  '/controller/new',
37357     save :    '/controller/update',
37358     destroy : '/controller/destroy_action'
37359 }
37360      * </code></pre>
37361      * <p>If the specific URL for a given CRUD action is undefined, the CRUD action request
37362      * will be directed to the configured <tt>{@link Ext.data.proxy.Server#url url}</tt>.</p>
37363      */
37364     
37365     /**
37366      * @ignore
37367      */
37368     constructor: function(config) {
37369         var me = this;
37370         
37371         config = config || {};
37372         this.addEvents(
37373             /**
37374              * @event exception
37375              * Fires when the server returns an exception
37376              * @param {Ext.data.proxy.Proxy} this
37377              * @param {Object} response The response from the AJAX request
37378              * @param {Ext.data.Operation} operation The operation that triggered request
37379              */
37380             'exception'
37381         );
37382         me.callParent([config]);
37383         
37384         /**
37385          * @cfg {Object} extraParams Extra parameters that will be included on every request. Individual requests with params
37386          * of the same name will override these params when they are in conflict.
37387          */
37388         me.extraParams = config.extraParams || {};
37389         
37390         me.api = config.api || {};
37391         
37392         //backwards compatibility, will be deprecated in 5.0
37393         me.nocache = me.noCache;
37394     },
37395     
37396     //in a ServerProxy all four CRUD operations are executed in the same manner, so we delegate to doRequest in each case
37397     create: function() {
37398         return this.doRequest.apply(this, arguments);
37399     },
37400     
37401     read: function() {
37402         return this.doRequest.apply(this, arguments);
37403     },
37404     
37405     update: function() {
37406         return this.doRequest.apply(this, arguments);
37407     },
37408     
37409     destroy: function() {
37410         return this.doRequest.apply(this, arguments);
37411     },
37412     
37413     /**
37414      * Creates and returns an Ext.data.Request object based on the options passed by the {@link Ext.data.Store Store}
37415      * that this Proxy is attached to.
37416      * @param {Ext.data.Operation} operation The {@link Ext.data.Operation Operation} object to execute
37417      * @return {Ext.data.Request} The request object
37418      */
37419     buildRequest: function(operation) {
37420         var params = Ext.applyIf(operation.params || {}, this.extraParams || {}),
37421             request;
37422         
37423         //copy any sorters, filters etc into the params so they can be sent over the wire
37424         params = Ext.applyIf(params, this.getParams(params, operation));
37425         
37426         if (operation.id && !params.id) {
37427             params.id = operation.id;
37428         }
37429         
37430         request = Ext.create('Ext.data.Request', {
37431             params   : params,
37432             action   : operation.action,
37433             records  : operation.records,
37434             operation: operation,
37435             url      : operation.url
37436         });
37437         
37438         request.url = this.buildUrl(request);
37439         
37440         /*
37441          * Save the request on the Operation. Operations don't usually care about Request and Response data, but in the
37442          * ServerProxy and any of its subclasses we add both request and response as they may be useful for further processing
37443          */
37444         operation.request = request;
37445         
37446         return request;
37447     },
37448     
37449     /**
37450      * 
37451      */
37452     processResponse: function(success, operation, request, response, callback, scope){
37453         var me = this,
37454             reader,
37455             result,
37456             records,
37457             length,
37458             mc,
37459             record,
37460             i;
37461             
37462         if (success === true) {
37463             reader = me.getReader();
37464             result = reader.read(me.extractResponseData(response));
37465             records = result.records;
37466             length = records.length;
37467             
37468             if (result.success !== false) {
37469                 mc = Ext.create('Ext.util.MixedCollection', true, function(r) {return r.getId();});
37470                 mc.addAll(operation.records);
37471                 for (i = 0; i < length; i++) {
37472                     record = mc.get(records[i].getId());
37473                     
37474                     if (record) {
37475                         record.beginEdit();
37476                         record.set(record.data);
37477                         record.endEdit(true);
37478                     }
37479                 }
37480                 
37481                 //see comment in buildRequest for why we include the response object here
37482                 Ext.apply(operation, {
37483                     response: response,
37484                     resultSet: result
37485                 });
37486                 
37487                 operation.setCompleted();
37488                 operation.setSuccessful();
37489             } else {
37490                 operation.setException(result.message);
37491                 me.fireEvent('exception', this, response, operation);
37492             }
37493         } else {
37494             me.setException(operation, response);
37495             me.fireEvent('exception', this, response, operation);              
37496         }
37497             
37498         //this callback is the one that was passed to the 'read' or 'write' function above
37499         if (typeof callback == 'function') {
37500             callback.call(scope || me, operation);
37501         }
37502             
37503         me.afterRequest(request, success);
37504     },
37505     
37506     /**
37507      * Sets up an exception on the operation
37508      * @private
37509      * @param {Ext.data.Operation} operation The operation
37510      * @param {Object} response The response
37511      */
37512     setException: function(operation, response){
37513         operation.setException({
37514             status: response.status,
37515             statusText: response.statusText
37516         });     
37517     },
37518     
37519     /**
37520      * Template method to allow subclasses to specify how to get the response for the reader.
37521      * @private
37522      * @param {Object} response The server response
37523      * @return {Mixed} The response data to be used by the reader
37524      */
37525     extractResponseData: function(response){
37526         return response; 
37527     },
37528     
37529     /**
37530      * Encode any values being sent to the server. Can be overridden in subclasses.
37531      * @private
37532      * @param {Array} An array of sorters/filters.
37533      * @return {Mixed} The encoded value
37534      */
37535     applyEncoding: function(value){
37536         return Ext.encode(value);
37537     },
37538     
37539     /**
37540      * Encodes the array of {@link Ext.util.Sorter} objects into a string to be sent in the request url. By default, 
37541      * this simply JSON-encodes the sorter data
37542      * @param {Array} sorters The array of {@link Ext.util.Sorter Sorter} objects
37543      * @return {String} The encoded sorters
37544      */
37545     encodeSorters: function(sorters) {
37546         var min = [],
37547             length = sorters.length,
37548             i = 0;
37549         
37550         for (; i < length; i++) {
37551             min[i] = {
37552                 property : sorters[i].property,
37553                 direction: sorters[i].direction
37554             };
37555         }
37556         return this.applyEncoding(min);
37557         
37558     },
37559     
37560     /**
37561      * Encodes the array of {@link Ext.util.Filter} objects into a string to be sent in the request url. By default, 
37562      * this simply JSON-encodes the filter data
37563      * @param {Array} sorters The array of {@link Ext.util.Filter Filter} objects
37564      * @return {String} The encoded filters
37565      */
37566     encodeFilters: function(filters) {
37567         var min = [],
37568             length = filters.length,
37569             i = 0;
37570         
37571         for (; i < length; i++) {
37572             min[i] = {
37573                 property: filters[i].property,
37574                 value   : filters[i].value
37575             };
37576         }
37577         return this.applyEncoding(min);
37578     },
37579     
37580     /**
37581      * @private
37582      * Copy any sorters, filters etc into the params so they can be sent over the wire
37583      */
37584     getParams: function(params, operation) {
37585         params = params || {};
37586         
37587         var me             = this,
37588             isDef          = Ext.isDefined,
37589             groupers       = operation.groupers,
37590             sorters        = operation.sorters,
37591             filters        = operation.filters,
37592             page           = operation.page,
37593             start          = operation.start,
37594             limit          = operation.limit,
37595             
37596             simpleSortMode = me.simpleSortMode,
37597             
37598             pageParam      = me.pageParam,
37599             startParam     = me.startParam,
37600             limitParam     = me.limitParam,
37601             groupParam     = me.groupParam,
37602             sortParam      = me.sortParam,
37603             filterParam    = me.filterParam,
37604             directionParam       = me.directionParam;
37605         
37606         if (pageParam && isDef(page)) {
37607             params[pageParam] = page;
37608         }
37609         
37610         if (startParam && isDef(start)) {
37611             params[startParam] = start;
37612         }
37613         
37614         if (limitParam && isDef(limit)) {
37615             params[limitParam] = limit;
37616         }
37617         
37618         if (groupParam && groupers && groupers.length > 0) {
37619             // Grouper is a subclass of sorter, so we can just use the sorter method
37620             params[groupParam] = me.encodeSorters(groupers);
37621         }
37622         
37623         if (sortParam && sorters && sorters.length > 0) {
37624             if (simpleSortMode) {
37625                 params[sortParam] = sorters[0].property;
37626                 params[directionParam] = sorters[0].direction;
37627             } else {
37628                 params[sortParam] = me.encodeSorters(sorters);
37629             }
37630             
37631         }
37632         
37633         if (filterParam && filters && filters.length > 0) {
37634             params[filterParam] = me.encodeFilters(filters);
37635         }
37636         
37637         return params;
37638     },
37639     
37640     /**
37641      * Generates a url based on a given Ext.data.Request object. By default, ServerProxy's buildUrl will
37642      * add the cache-buster param to the end of the url. Subclasses may need to perform additional modifications
37643      * to the url.
37644      * @param {Ext.data.Request} request The request object
37645      * @return {String} The url
37646      */
37647     buildUrl: function(request) {
37648         var me = this,
37649             url = me.getUrl(request);
37650         
37651         if (!url) {
37652             Ext.Error.raise("You are using a ServerProxy but have not supplied it with a url.");
37653         }
37654         
37655         if (me.noCache) {
37656             url = Ext.urlAppend(url, Ext.String.format("{0}={1}", me.cacheString, Ext.Date.now()));
37657         }
37658         
37659         return url;
37660     },
37661     
37662     /**
37663      * Get the url for the request taking into account the order of priority,
37664      * - The request
37665      * - The api
37666      * - The url
37667      * @private
37668      * @param {Ext.data.Request} request The request
37669      * @return {String} The url
37670      */
37671     getUrl: function(request){
37672         return request.url || this.api[request.action] || this.url;
37673     },
37674     
37675     /**
37676      * In ServerProxy subclasses, the {@link #create}, {@link #read}, {@link #update} and {@link #destroy} methods all pass
37677      * through to doRequest. Each ServerProxy subclass must implement the doRequest method - see {@link Ext.data.proxy.JsonP}
37678      * and {@link Ext.data.proxy.Ajax} for examples. This method carries the same signature as each of the methods that delegate to it.
37679      * @param {Ext.data.Operation} operation The Ext.data.Operation object
37680      * @param {Function} callback The callback function to call when the Operation has completed
37681      * @param {Object} scope The scope in which to execute the callback
37682      */
37683     doRequest: function(operation, callback, scope) {
37684         Ext.Error.raise("The doRequest function has not been implemented on your Ext.data.proxy.Server subclass. See src/data/ServerProxy.js for details");
37685     },
37686     
37687     /**
37688      * Optional callback function which can be used to clean up after a request has been completed.
37689      * @param {Ext.data.Request} request The Request object
37690      * @param {Boolean} success True if the request was successful
37691      */
37692     afterRequest: Ext.emptyFn,
37693     
37694     onDestroy: function() {
37695         Ext.destroy(this.reader, this.writer);
37696     }
37697 });
37698
37699 /**
37700  * @author Ed Spencer
37701  * @class Ext.data.proxy.Ajax
37702  * @extends Ext.data.proxy.Server
37703  * 
37704  * <p>AjaxProxy is one of the most widely-used ways of getting data into your application. It uses AJAX requests to 
37705  * load data from the server, usually to be placed into a {@link Ext.data.Store Store}. Let's take a look at a typical
37706  * setup. Here we're going to set up a Store that has an AjaxProxy. To prepare, we'll also set up a 
37707  * {@link Ext.data.Model Model}:</p>
37708  * 
37709 <pre><code>
37710 Ext.define('User', {
37711     extend: 'Ext.data.Model',
37712     fields: ['id', 'name', 'email']
37713 });
37714
37715 //The Store contains the AjaxProxy as an inline configuration
37716 var store = new Ext.data.Store({
37717     model: 'User',
37718     proxy: {
37719         type: 'ajax',
37720         url : 'users.json'
37721     }
37722 });
37723
37724 store.load();
37725 </code></pre>
37726  * 
37727  * <p>Our example is going to load user data into a Store, so we start off by defining a {@link Ext.data.Model Model}
37728  * with the fields that we expect the server to return. Next we set up the Store itself, along with a {@link #proxy}
37729  * configuration. This configuration was automatically turned into an Ext.data.proxy.Ajax instance, with the url we
37730  * specified being passed into AjaxProxy's constructor. It's as if we'd done this:</p>
37731  * 
37732 <pre><code>
37733 new Ext.data.proxy.Ajax({
37734     url: 'users.json',
37735     model: 'User',
37736     reader: 'json'
37737 });
37738 </code></pre>
37739  * 
37740  * <p>A couple of extra configurations appeared here - {@link #model} and {@link #reader}. These are set by default 
37741  * when we create the proxy via the Store - the Store already knows about the Model, and Proxy's default 
37742  * {@link Ext.data.reader.Reader Reader} is {@link Ext.data.reader.Json JsonReader}.</p>
37743  * 
37744  * <p>Now when we call store.load(), the AjaxProxy springs into action, making a request to the url we configured
37745  * ('users.json' in this case). As we're performing a read, it sends a GET request to that url (see {@link #actionMethods}
37746  * 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
37747  * POST request).</p>
37748  * 
37749  * <p><u>Limitations</u></p>
37750  * 
37751  * <p>AjaxProxy cannot be used to retrieve data from other domains. If your application is running on http://domainA.com
37752  * it cannot load data from http://domainB.com because browsers have a built-in security policy that prohibits domains
37753  * talking to each other via AJAX.</p>
37754  * 
37755  * <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
37756  * domain's web server and transparently forwards requests to http://domainB.com, making it look like they actually came
37757  * from http://domainA.com), you can use {@link Ext.data.proxy.JsonP} and a technique known as JSON-P (JSON with 
37758  * Padding), which can help you get around the problem so long as the server on http://domainB.com is set up to support
37759  * JSON-P responses. See {@link Ext.data.proxy.JsonP JsonPProxy}'s introduction docs for more details.</p>
37760  * 
37761  * <p><u>Readers and Writers</u></p>
37762  * 
37763  * <p>AjaxProxy can be configured to use any type of {@link Ext.data.reader.Reader Reader} to decode the server's response. If
37764  * no Reader is supplied, AjaxProxy will default to using a {@link Ext.data.reader.Json JsonReader}. Reader configuration
37765  * can be passed in as a simple object, which the Proxy automatically turns into a {@link Ext.data.reader.Reader Reader}
37766  * instance:</p>
37767  * 
37768 <pre><code>
37769 var proxy = new Ext.data.proxy.Ajax({
37770     model: 'User',
37771     reader: {
37772         type: 'xml',
37773         root: 'users'
37774     }
37775 });
37776
37777 proxy.getReader(); //returns an {@link Ext.data.reader.Xml XmlReader} instance based on the config we supplied
37778 </code></pre>
37779  * 
37780  * <p><u>Url generation</u></p>
37781  * 
37782  * <p>AjaxProxy automatically inserts any sorting, filtering, paging and grouping options into the url it generates for
37783  * each request. These are controlled with the following configuration options:</p>
37784  * 
37785  * <ul style="list-style-type: disc; padding-left: 20px;">
37786  *     <li>{@link #pageParam} - controls how the page number is sent to the server 
37787  *     (see also {@link #startParam} and {@link #limitParam})</li>
37788  *     <li>{@link #sortParam} - controls how sort information is sent to the server</li>
37789  *     <li>{@link #groupParam} - controls how grouping information is sent to the server</li>
37790  *     <li>{@link #filterParam} - controls how filter information is sent to the server</li>
37791  * </ul>
37792  * 
37793  * <p>Each request sent by AjaxProxy is described by an {@link Ext.data.Operation Operation}. To see how we can 
37794  * customize the generated urls, let's say we're loading the Proxy with the following Operation:</p>
37795  * 
37796 <pre><code>
37797 var operation = new Ext.data.Operation({
37798     action: 'read',
37799     page  : 2
37800 });
37801 </code></pre>
37802  * 
37803  * <p>Now we'll issue the request for this Operation by calling {@link #read}:</p>
37804  * 
37805 <pre><code>
37806 var proxy = new Ext.data.proxy.Ajax({
37807     url: '/users'
37808 });
37809
37810 proxy.read(operation); //GET /users?page=2
37811 </code></pre>
37812  * 
37813  * <p>Easy enough - the Proxy just copied the page property from the Operation. We can customize how this page data is
37814  * sent to the server:</p>
37815  * 
37816 <pre><code>
37817 var proxy = new Ext.data.proxy.Ajax({
37818     url: '/users',
37819     pagePage: 'pageNumber'
37820 });
37821
37822 proxy.read(operation); //GET /users?pageNumber=2
37823 </code></pre>
37824  * 
37825  * <p>Alternatively, our Operation could have been configured to send start and limit parameters instead of page:</p>
37826  * 
37827 <pre><code>
37828 var operation = new Ext.data.Operation({
37829     action: 'read',
37830     start : 50,
37831     limit : 25
37832 });
37833
37834 var proxy = new Ext.data.proxy.Ajax({
37835     url: '/users'
37836 });
37837
37838 proxy.read(operation); //GET /users?start=50&limit=25
37839 </code></pre>
37840  * 
37841  * <p>Again we can customize this url:</p>
37842  * 
37843 <pre><code>
37844 var proxy = new Ext.data.proxy.Ajax({
37845     url: '/users',
37846     startParam: 'startIndex',
37847     limitParam: 'limitIndex'
37848 });
37849
37850 proxy.read(operation); //GET /users?startIndex=50&limitIndex=25
37851 </code></pre>
37852  * 
37853  * <p>AjaxProxy will also send sort and filter information to the server. Let's take a look at how this looks with a
37854  * more expressive Operation object:</p>
37855  * 
37856 <pre><code>
37857 var operation = new Ext.data.Operation({
37858     action: 'read',
37859     sorters: [
37860         new Ext.util.Sorter({
37861             property : 'name',
37862             direction: 'ASC'
37863         }),
37864         new Ext.util.Sorter({
37865             property : 'age',
37866             direction: 'DESC'
37867         })
37868     ],
37869     filters: [
37870         new Ext.util.Filter({
37871             property: 'eyeColor',
37872             value   : 'brown'
37873         })
37874     ]
37875 });
37876 </code></pre>
37877  * 
37878  * <p>This is the type of object that is generated internally when loading a {@link Ext.data.Store Store} with sorters
37879  * and filters defined. By default the AjaxProxy will JSON encode the sorters and filters, resulting in something like
37880  * this (note that the url is escaped before sending the request, but is left unescaped here for clarity):</p>
37881  * 
37882 <pre><code>
37883 var proxy = new Ext.data.proxy.Ajax({
37884     url: '/users'
37885 });
37886
37887 proxy.read(operation); //GET /users?sort=[{"property":"name","direction":"ASC"},{"property":"age","direction":"DESC"}]&filter=[{"property":"eyeColor","value":"brown"}]
37888 </code></pre>
37889  * 
37890  * <p>We can again customize how this is created by supplying a few configuration options. Let's say our server is set 
37891  * up to receive sorting information is a format like "sortBy=name#ASC,age#DESC". We can configure AjaxProxy to provide
37892  * that format like this:</p>
37893  * 
37894  <pre><code>
37895  var proxy = new Ext.data.proxy.Ajax({
37896      url: '/users',
37897      sortParam: 'sortBy',
37898      filterParam: 'filterBy',
37899
37900      //our custom implementation of sorter encoding - turns our sorters into "name#ASC,age#DESC"
37901      encodeSorters: function(sorters) {
37902          var length   = sorters.length,
37903              sortStrs = [],
37904              sorter, i;
37905
37906          for (i = 0; i < length; i++) {
37907              sorter = sorters[i];
37908
37909              sortStrs[i] = sorter.property + '#' + sorter.direction
37910          }
37911
37912          return sortStrs.join(",");
37913      }
37914  });
37915
37916  proxy.read(operation); //GET /users?sortBy=name#ASC,age#DESC&filterBy=[{"property":"eyeColor","value":"brown"}]
37917  </code></pre>
37918  * 
37919  * <p>We can also provide a custom {@link #encodeFilters} function to encode our filters.</p>
37920  * 
37921  * @constructor
37922  * 
37923  * <p>Note that if this HttpProxy is being used by a {@link Ext.data.Store Store}, then the
37924  * Store's call to {@link #load} will override any specified <tt>callback</tt> and <tt>params</tt>
37925  * options. In this case, use the Store's {@link Ext.data.Store#events events} to modify parameters,
37926  * or react to loading events. The Store's {@link Ext.data.Store#baseParams baseParams} may also be
37927  * used to pass parameters known at instantiation time.</p>
37928  * 
37929  * <p>If an options parameter is passed, the singleton {@link Ext.Ajax} object will be used to make
37930  * the request.</p>
37931  */
37932 Ext.define('Ext.data.proxy.Ajax', {
37933     requires: ['Ext.util.MixedCollection', 'Ext.Ajax'],
37934     extend: 'Ext.data.proxy.Server',
37935     alias: 'proxy.ajax',
37936     alternateClassName: ['Ext.data.HttpProxy', 'Ext.data.AjaxProxy'],
37937     
37938     /**
37939      * @property actionMethods
37940      * Mapping of action name to HTTP request method. In the basic AjaxProxy these are set to 'GET' for 'read' actions and 'POST' 
37941      * for 'create', 'update' and 'destroy' actions. The {@link Ext.data.proxy.Rest} maps these to the correct RESTful methods.
37942      */
37943     actionMethods: {
37944         create : 'POST',
37945         read   : 'GET',
37946         update : 'POST',
37947         destroy: 'POST'
37948     },
37949     
37950     /**
37951      * @cfg {Object} headers Any headers to add to the Ajax request. Defaults to <tt>undefined</tt>.
37952      */
37953     
37954     /**
37955      * @ignore
37956      */
37957     doRequest: function(operation, callback, scope) {
37958         var writer  = this.getWriter(),
37959             request = this.buildRequest(operation, callback, scope);
37960             
37961         if (operation.allowWrite()) {
37962             request = writer.write(request);
37963         }
37964         
37965         Ext.apply(request, {
37966             headers       : this.headers,
37967             timeout       : this.timeout,
37968             scope         : this,
37969             callback      : this.createRequestCallback(request, operation, callback, scope),
37970             method        : this.getMethod(request),
37971             disableCaching: false // explicitly set it to false, ServerProxy handles caching
37972         });
37973         
37974         Ext.Ajax.request(request);
37975         
37976         return request;
37977     },
37978     
37979     /**
37980      * Returns the HTTP method name for a given request. By default this returns based on a lookup on {@link #actionMethods}.
37981      * @param {Ext.data.Request} request The request object
37982      * @return {String} The HTTP method to use (should be one of 'GET', 'POST', 'PUT' or 'DELETE')
37983      */
37984     getMethod: function(request) {
37985         return this.actionMethods[request.action];
37986     },
37987     
37988     /**
37989      * @private
37990      * TODO: This is currently identical to the JsonPProxy version except for the return function's signature. There is a lot
37991      * of code duplication inside the returned function so we need to find a way to DRY this up.
37992      * @param {Ext.data.Request} request The Request object
37993      * @param {Ext.data.Operation} operation The Operation being executed
37994      * @param {Function} callback The callback function to be called when the request completes. This is usually the callback
37995      * passed to doRequest
37996      * @param {Object} scope The scope in which to execute the callback function
37997      * @return {Function} The callback function
37998      */
37999     createRequestCallback: function(request, operation, callback, scope) {
38000         var me = this;
38001         
38002         return function(options, success, response) {
38003             me.processResponse(success, operation, request, response, callback, scope);
38004         };
38005     }
38006 }, function() {
38007     //backwards compatibility, remove in Ext JS 5.0
38008     Ext.data.HttpProxy = this;
38009 });
38010
38011 /**
38012  * @author Ed Spencer
38013  * @class Ext.data.Model
38014  *
38015  * <p>A Model represents some object that your application manages. For example, one might define a Model for Users, Products,
38016  * 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},
38017  * and are used by {@link Ext.data.Store stores}, which are in turn used by many of the data-bound components in Ext.</p>
38018  *
38019  * <p>Models are defined as a set of fields and any arbitrary methods and properties relevant to the model. For example:</p>
38020  *
38021 <pre><code>
38022 Ext.define('User', {
38023     extend: 'Ext.data.Model',
38024     fields: [
38025         {name: 'name',  type: 'string'},
38026         {name: 'age',   type: 'int'},
38027         {name: 'phone', type: 'string'},
38028         {name: 'alive', type: 'boolean', defaultValue: true}
38029     ],
38030
38031     changeName: function() {
38032         var oldName = this.get('name'),
38033             newName = oldName + " The Barbarian";
38034
38035         this.set('name', newName);
38036     }
38037 });
38038 </code></pre>
38039 *
38040 * <p>The fields array is turned into a {@link Ext.util.MixedCollection MixedCollection} automatically by the {@link Ext.ModelManager ModelManager}, and all
38041 * other functions and properties are copied to the new Model's prototype.</p>
38042 *
38043 * <p>Now we can create instances of our User model and call any model logic we defined:</p>
38044 *
38045 <pre><code>
38046 var user = Ext.ModelManager.create({
38047     name : 'Conan',
38048     age  : 24,
38049     phone: '555-555-5555'
38050 }, 'User');
38051
38052 user.changeName();
38053 user.get('name'); //returns "Conan The Barbarian"
38054 </code></pre>
38055  *
38056  * <p><u>Validations</u></p>
38057  *
38058  * <p>Models have built-in support for validations, which are executed against the validator functions in
38059  * {@link Ext.data.validations} ({@link Ext.data.validations see all validation functions}). Validations are easy to add to models:</p>
38060  *
38061 <pre><code>
38062 Ext.define('User', {
38063     extend: 'Ext.data.Model',
38064     fields: [
38065         {name: 'name',     type: 'string'},
38066         {name: 'age',      type: 'int'},
38067         {name: 'phone',    type: 'string'},
38068         {name: 'gender',   type: 'string'},
38069         {name: 'username', type: 'string'},
38070         {name: 'alive',    type: 'boolean', defaultValue: true}
38071     ],
38072
38073     validations: [
38074         {type: 'presence',  field: 'age'},
38075         {type: 'length',    field: 'name',     min: 2},
38076         {type: 'inclusion', field: 'gender',   list: ['Male', 'Female']},
38077         {type: 'exclusion', field: 'username', list: ['Admin', 'Operator']},
38078         {type: 'format',    field: 'username', matcher: /([a-z]+)[0-9]{2,3}/}
38079     ]
38080 });
38081 </code></pre>
38082  *
38083  * <p>The validations can be run by simply calling the {@link #validate} function, which returns a {@link Ext.data.Errors}
38084  * object:</p>
38085  *
38086 <pre><code>
38087 var instance = Ext.ModelManager.create({
38088     name: 'Ed',
38089     gender: 'Male',
38090     username: 'edspencer'
38091 }, 'User');
38092
38093 var errors = instance.validate();
38094 </code></pre>
38095  *
38096  * <p><u>Associations</u></p>
38097  *
38098  * <p>Models can have associations with other Models via {@link Ext.data.BelongsToAssociation belongsTo} and
38099  * {@link Ext.data.HasManyAssociation hasMany} associations. For example, let's say we're writing a blog administration
38100  * application which deals with Users, Posts and Comments. We can express the relationships between these models like this:</p>
38101  *
38102 <pre><code>
38103 Ext.define('Post', {
38104     extend: 'Ext.data.Model',
38105     fields: ['id', 'user_id'],
38106
38107     belongsTo: 'User',
38108     hasMany  : {model: 'Comment', name: 'comments'}
38109 });
38110
38111 Ext.define('Comment', {
38112     extend: 'Ext.data.Model',
38113     fields: ['id', 'user_id', 'post_id'],
38114
38115     belongsTo: 'Post'
38116 });
38117
38118 Ext.define('User', {
38119     extend: 'Ext.data.Model',
38120     fields: ['id'],
38121
38122     hasMany: [
38123         'Post',
38124         {model: 'Comment', name: 'comments'}
38125     ]
38126 });
38127 </code></pre>
38128  *
38129  * <p>See the docs for {@link Ext.data.BelongsToAssociation} and {@link Ext.data.HasManyAssociation} for details on the usage
38130  * and configuration of associations. Note that associations can also be specified like this:</p>
38131  *
38132 <pre><code>
38133 Ext.define('User', {
38134     extend: 'Ext.data.Model',
38135     fields: ['id'],
38136
38137     associations: [
38138         {type: 'hasMany', model: 'Post',    name: 'posts'},
38139         {type: 'hasMany', model: 'Comment', name: 'comments'}
38140     ]
38141 });
38142 </code></pre>
38143  *
38144  * <p><u>Using a Proxy</u></p>
38145  *
38146  * <p>Models are great for representing types of data and relationships, but sooner or later we're going to want to
38147  * load or save that data somewhere. All loading and saving of data is handled via a {@link Ext.data.proxy.Proxy Proxy},
38148  * which can be set directly on the Model:</p>
38149  *
38150 <pre><code>
38151 Ext.define('User', {
38152     extend: 'Ext.data.Model',
38153     fields: ['id', 'name', 'email'],
38154
38155     proxy: {
38156         type: 'rest',
38157         url : '/users'
38158     }
38159 });
38160 </code></pre>
38161  *
38162  * <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
38163  * RESTful backend. Let's see how this works:</p>
38164  *
38165 <pre><code>
38166 var user = Ext.ModelManager.create({name: 'Ed Spencer', email: 'ed@sencha.com'}, 'User');
38167
38168 user.save(); //POST /users
38169 </code></pre>
38170  *
38171  * <p>Calling {@link #save} on the new Model instance tells the configured RestProxy that we wish to persist this
38172  * Model's data onto our server. RestProxy figures out that this Model hasn't been saved before because it doesn't
38173  * have an id, and performs the appropriate action - in this case issuing a POST request to the url we configured
38174  * (/users). We configure any Proxy on any Model and always follow this API - see {@link Ext.data.proxy.Proxy} for a full
38175  * list.</p>
38176  *
38177  * <p>Loading data via the Proxy is equally easy:</p>
38178  *
38179 <pre><code>
38180 //get a reference to the User model class
38181 var User = Ext.ModelManager.getModel('User');
38182
38183 //Uses the configured RestProxy to make a GET request to /users/123
38184 User.load(123, {
38185     success: function(user) {
38186         console.log(user.getId()); //logs 123
38187     }
38188 });
38189 </code></pre>
38190  *
38191  * <p>Models can also be updated and destroyed easily:</p>
38192  *
38193 <pre><code>
38194 //the user Model we loaded in the last snippet:
38195 user.set('name', 'Edward Spencer');
38196
38197 //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
38198 user.save({
38199     success: function() {
38200         console.log('The User was updated');
38201     }
38202 });
38203
38204 //tells the Proxy to destroy the Model. Performs a DELETE request to /users/123
38205 user.destroy({
38206     success: function() {
38207         console.log('The User was destroyed!');
38208     }
38209 });
38210 </code></pre>
38211  *
38212  * <p><u>Usage in Stores</u></p>
38213  *
38214  * <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
38215  * by creating a {@link Ext.data.Store Store}:</p>
38216  *
38217 <pre><code>
38218 var store = new Ext.data.Store({
38219     model: 'User'
38220 });
38221
38222 //uses the Proxy we set up on Model to load the Store data
38223 store.load();
38224 </code></pre>
38225  *
38226  * <p>A Store is just a collection of Model instances - usually loaded from a server somewhere. Store can also maintain
38227  * a set of added, updated and removed Model instances to be synchronized with the server via the Proxy. See the
38228  * {@link Ext.data.Store Store docs} for more information on Stores.</p>
38229  *
38230  * @constructor
38231  * @param {Object} data An object containing keys corresponding to this model's fields, and their associated values
38232  * @param {Number} id Optional unique ID to assign to this model instance
38233  */
38234 Ext.define('Ext.data.Model', {
38235     alternateClassName: 'Ext.data.Record',
38236     
38237     mixins: {
38238         observable: 'Ext.util.Observable'
38239     },
38240
38241     requires: [
38242         'Ext.ModelManager',
38243         'Ext.data.Field',
38244         'Ext.data.Errors',
38245         'Ext.data.Operation',
38246         'Ext.data.validations',
38247         'Ext.data.proxy.Ajax',
38248         'Ext.util.MixedCollection'
38249     ],
38250
38251     onClassExtended: function(cls, data) {
38252         var onBeforeClassCreated = data.onBeforeClassCreated;
38253
38254         data.onBeforeClassCreated = function(cls, data) {
38255             var me = this,
38256                 name = Ext.getClassName(cls),
38257                 prototype = cls.prototype,
38258                 superCls = cls.prototype.superclass,
38259
38260                 validations = data.validations || [],
38261                 fields = data.fields || [],
38262                 associations = data.associations || [],
38263                 belongsTo = data.belongsTo,
38264                 hasMany = data.hasMany,
38265
38266                 fieldsMixedCollection = new Ext.util.MixedCollection(false, function(field) {
38267                     return field.name;
38268                 }),
38269
38270                 associationsMixedCollection = new Ext.util.MixedCollection(false, function(association) {
38271                     return association.name;
38272                 }),
38273
38274                 superValidations = superCls.validations,
38275                 superFields = superCls.fields,
38276                 superAssociations = superCls.associations,
38277
38278                 association, i, ln,
38279                 dependencies = [];
38280
38281             // Save modelName on class and its prototype
38282             cls.modelName = name;
38283             prototype.modelName = name;
38284
38285             // Merge the validations of the superclass and the new subclass
38286             if (superValidations) {
38287                 validations = superValidations.concat(validations);
38288             }
38289
38290             data.validations = validations;
38291
38292             // Merge the fields of the superclass and the new subclass
38293             if (superFields) {
38294                 fields = superFields.items.concat(fields);
38295             }
38296
38297             for (i = 0, ln = fields.length; i < ln; ++i) {
38298                 fieldsMixedCollection.add(new Ext.data.Field(fields[i]));
38299             }
38300
38301             data.fields = fieldsMixedCollection;
38302
38303             //associations can be specified in the more convenient format (e.g. not inside an 'associations' array).
38304             //we support that here
38305             if (belongsTo) {
38306                 belongsTo = Ext.Array.from(belongsTo);
38307
38308                 for (i = 0, ln = belongsTo.length; i < ln; ++i) {
38309                     association = belongsTo[i];
38310
38311                     if (!Ext.isObject(association)) {
38312                         association = {model: association};
38313                     }
38314
38315                     association.type = 'belongsTo';
38316                     associations.push(association);
38317                 }
38318
38319                 delete data.belongsTo;
38320             }
38321
38322             if (hasMany) {
38323                 hasMany = Ext.Array.from(hasMany);
38324                 for (i = 0, ln = hasMany.length; i < ln; ++i) {
38325                     association = hasMany[i];
38326
38327                     if (!Ext.isObject(association)) {
38328                         association = {model: association};
38329                     }
38330
38331                     association.type = 'hasMany';
38332                     associations.push(association);
38333                 }
38334
38335                 delete data.hasMany;
38336             }
38337
38338             if (superAssociations) {
38339                 associations = superAssociations.items.concat(associations);
38340             }
38341
38342             for (i = 0, ln = associations.length; i < ln; ++i) {
38343                 dependencies.push('association.' + associations[i].type.toLowerCase());
38344             }
38345
38346             if (data.proxy) {
38347                 if (typeof data.proxy === 'string') {
38348                     dependencies.push('proxy.' + data.proxy);
38349                 }
38350                 else if (typeof data.proxy.type === 'string') {
38351                     dependencies.push('proxy.' + data.proxy.type);
38352                 }
38353             }
38354
38355             Ext.require(dependencies, function() {
38356                 Ext.ModelManager.registerType(name, cls);
38357
38358                 for (i = 0, ln = associations.length; i < ln; ++i) {
38359                     association = associations[i];
38360
38361                     Ext.apply(association, {
38362                         ownerModel: name,
38363                         associatedModel: association.model
38364                     });
38365
38366                     if (Ext.ModelManager.getModel(association.model) === undefined) {
38367                         Ext.ModelManager.registerDeferredAssociation(association);
38368                     } else {
38369                         associationsMixedCollection.add(Ext.data.Association.create(association));
38370                     }
38371                 }
38372
38373                 data.associations = associationsMixedCollection;
38374
38375                 onBeforeClassCreated.call(me, cls, data);
38376
38377                 cls.setProxy(cls.prototype.proxy || cls.prototype.defaultProxyType);
38378
38379                 // Fire the onModelDefined template method on ModelManager
38380                 Ext.ModelManager.onModelDefined(cls);
38381             });
38382         }
38383     },
38384
38385     inheritableStatics: {
38386         /**
38387          * Sets the Proxy to use for this model. Accepts any options that can be accepted by {@link Ext#createByAlias Ext.createByAlias}
38388          * @param {String/Object/Ext.data.proxy.Proxy} proxy The proxy
38389          * @static
38390          */
38391         setProxy: function(proxy) {
38392             //make sure we have an Ext.data.proxy.Proxy object
38393             if (!proxy.isProxy) {
38394                 if (typeof proxy == "string") {
38395                     proxy = {
38396                         type: proxy
38397                     };
38398                 }
38399                 proxy = Ext.createByAlias("proxy." + proxy.type, proxy);
38400             }
38401             proxy.setModel(this);
38402             this.proxy = this.prototype.proxy = proxy;
38403
38404             return proxy;
38405         },
38406
38407         /**
38408          * Returns the configured Proxy for this Model
38409          * @return {Ext.data.proxy.Proxy} The proxy
38410          */
38411         getProxy: function() {
38412             return this.proxy;
38413         },
38414
38415         /**
38416          * <b>Static</b>. Asynchronously loads a model instance by id. Sample usage:
38417     <pre><code>
38418     MyApp.User = Ext.define('User', {
38419         extend: 'Ext.data.Model',
38420         fields: [
38421             {name: 'id', type: 'int'},
38422             {name: 'name', type: 'string'}
38423         ]
38424     });
38425
38426     MyApp.User.load(10, {
38427         scope: this,
38428         failure: function(record, operation) {
38429             //do something if the load failed
38430         },
38431         success: function(record, operation) {
38432             //do something if the load succeeded
38433         },
38434         callback: function(record, operation) {
38435             //do something whether the load succeeded or failed
38436         }
38437     });
38438     </code></pre>
38439          * @param {Number} id The id of the model to load
38440          * @param {Object} config Optional config object containing success, failure and callback functions, plus optional scope
38441          * @member Ext.data.Model
38442          * @method load
38443          * @static
38444          */
38445         load: function(id, config) {
38446             config = Ext.apply({}, config);
38447             config = Ext.applyIf(config, {
38448                 action: 'read',
38449                 id    : id
38450             });
38451
38452             var operation  = Ext.create('Ext.data.Operation', config),
38453                 scope      = config.scope || this,
38454                 record     = null,
38455                 callback;
38456
38457             callback = function(operation) {
38458                 if (operation.wasSuccessful()) {
38459                     record = operation.getRecords()[0];
38460                     Ext.callback(config.success, scope, [record, operation]);
38461                 } else {
38462                     Ext.callback(config.failure, scope, [record, operation]);
38463                 }
38464                 Ext.callback(config.callback, scope, [record, operation]);
38465             };
38466
38467             this.proxy.read(operation, callback, this);
38468         }
38469     },
38470
38471     statics: {
38472         PREFIX : 'ext-record',
38473         AUTO_ID: 1,
38474         EDIT   : 'edit',
38475         REJECT : 'reject',
38476         COMMIT : 'commit',
38477
38478         /**
38479          * Generates a sequential id. This method is typically called when a record is {@link #create}d
38480          * and {@link #Record no id has been specified}. The id will automatically be assigned
38481          * to the record. The returned id takes the form:
38482          * <tt>&#123;PREFIX}-&#123;AUTO_ID}</tt>.<div class="mdetail-params"><ul>
38483          * <li><b><tt>PREFIX</tt></b> : String<p class="sub-desc"><tt>Ext.data.Model.PREFIX</tt>
38484          * (defaults to <tt>'ext-record'</tt>)</p></li>
38485          * <li><b><tt>AUTO_ID</tt></b> : String<p class="sub-desc"><tt>Ext.data.Model.AUTO_ID</tt>
38486          * (defaults to <tt>1</tt> initially)</p></li>
38487          * </ul></div>
38488          * @param {Ext.data.Model} rec The record being created.  The record does not exist, it's a {@link #phantom}.
38489          * @return {String} auto-generated string id, <tt>"ext-record-i++'</tt>;
38490          * @static
38491          */
38492         id: function(rec) {
38493             var id = [this.PREFIX, '-', this.AUTO_ID++].join('');
38494             rec.phantom = true;
38495             rec.internalId = id;
38496             return id;
38497         }
38498     },
38499     
38500     /**
38501      * Internal flag used to track whether or not the model instance is currently being edited. Read-only
38502      * @property editing
38503      * @type Boolean
38504      */
38505     editing : false,
38506
38507     /**
38508      * Readonly flag - true if this Record has been modified.
38509      * @type Boolean
38510      */
38511     dirty : false,
38512
38513     /**
38514      * @cfg {String} persistanceProperty The property on this Persistable object that its data is saved to.
38515      * Defaults to 'data' (e.g. all persistable data resides in this.data.)
38516      */
38517     persistanceProperty: 'data',
38518
38519     evented: false,
38520     isModel: true,
38521
38522     /**
38523      * <tt>true</tt> when the record does not yet exist in a server-side database (see
38524      * {@link #setDirty}).  Any record which has a real database pk set as its id property
38525      * is NOT a phantom -- it's real.
38526      * @property phantom
38527      * @type {Boolean}
38528      */
38529     phantom : false,
38530
38531     /**
38532      * @cfg {String} idProperty The name of the field treated as this Model's unique id (defaults to 'id').
38533      */
38534     idProperty: 'id',
38535
38536     /**
38537      * The string type of the default Model Proxy. Defaults to 'ajax'
38538      * @property defaultProxyType
38539      * @type String
38540      */
38541     defaultProxyType: 'ajax',
38542
38543     /**
38544      * An array of the fields defined on this model
38545      * @property fields
38546      * @type {Array}
38547      */
38548
38549     constructor: function(data, id) {
38550         data = data || {};
38551         
38552         var me = this,
38553             fields,
38554             length,
38555             field,
38556             name,
38557             i,
38558             isArray = Ext.isArray(data),
38559             newData = isArray ? {} : null; // to hold mapped array data if needed
38560
38561         /**
38562          * An internal unique ID for each Model instance, used to identify Models that don't have an ID yet
38563          * @property internalId
38564          * @type String
38565          * @private
38566          */
38567         me.internalId = (id || id === 0) ? id : Ext.data.Model.id(me);
38568
38569         Ext.applyIf(me, {
38570             data: {}    
38571         });
38572         
38573         /**
38574          * Key: value pairs of all fields whose values have changed
38575          * @property modified
38576          * @type Object
38577          */
38578         me.modified = {};
38579
38580         me[me.persistanceProperty] = {};
38581
38582         me.mixins.observable.constructor.call(me);
38583
38584         //add default field values if present
38585         fields = me.fields.items;
38586         length = fields.length;
38587
38588         for (i = 0; i < length; i++) {
38589             field = fields[i];
38590             name  = field.name;
38591
38592             if (isArray){ 
38593                 // Have to map array data so the values get assigned to the named fields
38594                 // rather than getting set as the field names with undefined values.
38595                 newData[name] = data[i];
38596             }
38597             else if (data[name] === undefined) {
38598                 data[name] = field.defaultValue;
38599             }
38600         }
38601
38602         me.set(newData || data);
38603         // clear any dirty/modified since we're initializing
38604         me.dirty = false;
38605         me.modified = {};
38606
38607         if (me.getId()) {
38608             me.phantom = false;
38609         }
38610
38611         if (typeof me.init == 'function') {
38612             me.init();
38613         }
38614
38615         me.id = me.modelName + '-' + me.internalId;
38616
38617         Ext.ModelManager.register(me);
38618     },
38619     
38620     /**
38621      * Returns the value of the given field
38622      * @param {String} fieldName The field to fetch the value for
38623      * @return {Mixed} The value
38624      */
38625     get: function(field) {
38626         return this[this.persistanceProperty][field];
38627     },
38628     
38629     /**
38630      * Sets the given field to the given value, marks the instance as dirty
38631      * @param {String|Object} fieldName The field to set, or an object containing key/value pairs
38632      * @param {Mixed} value The value to set
38633      */
38634     set: function(fieldName, value) {
38635         var me = this,
38636             fields = me.fields,
38637             modified = me.modified,
38638             convertFields = [],
38639             field, key, i, currentValue;
38640
38641         /*
38642          * If we're passed an object, iterate over that object. NOTE: we pull out fields with a convert function and
38643          * set those last so that all other possible data is set before the convert function is called
38644          */
38645         if (arguments.length == 1 && Ext.isObject(fieldName)) {
38646             for (key in fieldName) {
38647                 if (fieldName.hasOwnProperty(key)) {
38648                 
38649                     //here we check for the custom convert function. Note that if a field doesn't have a convert function,
38650                     //we default it to its type's convert function, so we have to check that here. This feels rather dirty.
38651                     field = fields.get(key);
38652                     if (field && field.convert !== field.type.convert) {
38653                         convertFields.push(key);
38654                         continue;
38655                     }
38656                     
38657                     me.set(key, fieldName[key]);
38658                 }
38659             }
38660
38661             for (i = 0; i < convertFields.length; i++) {
38662                 field = convertFields[i];
38663                 me.set(field, fieldName[field]);
38664             }
38665
38666         } else {
38667             if (fields) {
38668                 field = fields.get(fieldName);
38669
38670                 if (field && field.convert) {
38671                     value = field.convert(value, me);
38672                 }
38673             }
38674             currentValue = me.get(fieldName);
38675             me[me.persistanceProperty][fieldName] = value;
38676             
38677             if (field && field.persist && !me.isEqual(currentValue, value)) {
38678                 me.dirty = true;
38679                 me.modified[fieldName] = currentValue;
38680             }
38681
38682             if (!me.editing) {
38683                 me.afterEdit();
38684             }
38685         }
38686     },
38687     
38688     /**
38689      * Checks if two values are equal, taking into account certain
38690      * special factors, for example dates.
38691      * @private
38692      * @param {Object} a The first value
38693      * @param {Object} b The second value
38694      * @return {Boolean} True if the values are equal
38695      */
38696     isEqual: function(a, b){
38697         if (Ext.isDate(a) && Ext.isDate(b)) {
38698             return a.getTime() === b.getTime();
38699         }
38700         return a === b;
38701     },
38702     
38703     /**
38704      * Begin an edit. While in edit mode, no events (e.g.. the <code>update</code> event)
38705      * are relayed to the containing store. When an edit has begun, it must be followed
38706      * by either {@link #endEdit} or {@link #cancelEdit}.
38707      */
38708     beginEdit : function(){
38709         var me = this;
38710         if (!me.editing) {
38711             me.editing = true;
38712             me.dirtySave = me.dirty;
38713             me.dataSave = Ext.apply({}, me[me.persistanceProperty]);
38714             me.modifiedSave = Ext.apply({}, me.modified);
38715         }
38716     },
38717     
38718     /**
38719      * Cancels all changes made in the current edit operation.
38720      */
38721     cancelEdit : function(){
38722         var me = this;
38723         if (me.editing) {
38724             me.editing = false;
38725             // reset the modified state, nothing changed since the edit began
38726             me.modified = me.modifiedSave;
38727             me[me.persistanceProperty] = me.dataSave;
38728             me.dirty = me.dirtySave;
38729             delete me.modifiedSave;
38730             delete me.dataSave;
38731             delete me.dirtySave;
38732         }
38733     },
38734     
38735     /**
38736      * End an edit. If any data was modified, the containing store is notified
38737      * (ie, the store's <code>update</code> event will fire).
38738      * @param {Boolean} silent True to not notify the store of the change
38739      */
38740     endEdit : function(silent){
38741         var me = this;
38742         if (me.editing) {
38743             me.editing = false;
38744             delete me.modifiedSave;
38745             delete me.dataSave;
38746             delete me.dirtySave;
38747             if (silent !== true && me.dirty) {
38748                 me.afterEdit();
38749             }
38750         }
38751     },
38752     
38753     /**
38754      * Gets a hash of only the fields that have been modified since this Model was created or commited.
38755      * @return Object
38756      */
38757     getChanges : function(){
38758         var modified = this.modified,
38759             changes  = {},
38760             field;
38761
38762         for (field in modified) {
38763             if (modified.hasOwnProperty(field)){
38764                 changes[field] = this.get(field);
38765             }
38766         }
38767
38768         return changes;
38769     },
38770     
38771     /**
38772      * Returns <tt>true</tt> if the passed field name has been <code>{@link #modified}</code>
38773      * since the load or last commit.
38774      * @param {String} fieldName {@link Ext.data.Field#name}
38775      * @return {Boolean}
38776      */
38777     isModified : function(fieldName) {
38778         return this.modified.hasOwnProperty(fieldName);
38779     },
38780     
38781     /**
38782      * <p>Marks this <b>Record</b> as <code>{@link #dirty}</code>.  This method
38783      * is used interally when adding <code>{@link #phantom}</code> records to a
38784      * {@link Ext.data.Store#writer writer enabled store}.</p>
38785      * <br><p>Marking a record <code>{@link #dirty}</code> causes the phantom to
38786      * be returned by {@link Ext.data.Store#getModifiedRecords} where it will
38787      * have a create action composed for it during {@link Ext.data.Store#save store save}
38788      * operations.</p>
38789      */
38790     setDirty : function() {
38791         var me = this,
38792             name;
38793         
38794         me.dirty = true;
38795
38796         me.fields.each(function(field) {
38797             if (field.persist) {
38798                 name = field.name;
38799                 me.modified[name] = me.get(name);
38800             }
38801         }, me);
38802     },
38803
38804     markDirty : function() {
38805         if (Ext.isDefined(Ext.global.console)) {
38806             Ext.global.console.warn('Ext.data.Model: markDirty has been deprecated. Use setDirty instead.');
38807         }
38808         return this.setDirty.apply(this, arguments);
38809     },
38810     
38811     /**
38812      * Usually called by the {@link Ext.data.Store} to which this model instance has been {@link #join joined}.
38813      * Rejects all changes made to the model instance since either creation, or the last commit operation.
38814      * Modified fields are reverted to their original values.
38815      * <p>Developers should subscribe to the {@link Ext.data.Store#update} event
38816      * to have their code notified of reject operations.</p>
38817      * @param {Boolean} silent (optional) True to skip notification of the owning
38818      * store of the change (defaults to false)
38819      */
38820     reject : function(silent) {
38821         var me = this,
38822             modified = me.modified,
38823             field;
38824
38825         for (field in modified) {
38826             if (modified.hasOwnProperty(field)) {
38827                 if (typeof modified[field] != "function") {
38828                     me[me.persistanceProperty][field] = modified[field];
38829                 }
38830             }
38831         }
38832
38833         me.dirty = false;
38834         me.editing = false;
38835         me.modified = {};
38836
38837         if (silent !== true) {
38838             me.afterReject();
38839         }
38840     },
38841
38842     /**
38843      * Usually called by the {@link Ext.data.Store} which owns the model instance.
38844      * Commits all changes made to the instance since either creation or the last commit operation.
38845      * <p>Developers should subscribe to the {@link Ext.data.Store#update} event
38846      * to have their code notified of commit operations.</p>
38847      * @param {Boolean} silent (optional) True to skip notification of the owning
38848      * store of the change (defaults to false)
38849      */
38850     commit : function(silent) {
38851         var me = this;
38852         
38853         me.dirty = false;
38854         me.editing = false;
38855
38856         me.modified = {};
38857
38858         if (silent !== true) {
38859             me.afterCommit();
38860         }
38861     },
38862
38863     /**
38864      * Creates a copy (clone) of this Model instance.
38865      * @param {String} id (optional) A new id, defaults to the id
38866      * of the instance being copied. See <code>{@link #id}</code>.
38867      * To generate a phantom instance with a new id use:<pre><code>
38868 var rec = record.copy(); // clone the record
38869 Ext.data.Model.id(rec); // automatically generate a unique sequential id
38870      * </code></pre>
38871      * @return {Record}
38872      */
38873     copy : function(newId) {
38874         var me = this;
38875         
38876         return new me.self(Ext.apply({}, me[me.persistanceProperty]), newId || me.internalId);
38877     },
38878
38879     /**
38880      * Sets the Proxy to use for this model. Accepts any options that can be accepted by {@link Ext#createByAlias Ext.createByAlias}
38881      * @param {String/Object/Ext.data.proxy.Proxy} proxy The proxy
38882      * @static
38883      */
38884     setProxy: function(proxy) {
38885         //make sure we have an Ext.data.proxy.Proxy object
38886         if (!proxy.isProxy) {
38887             if (typeof proxy === "string") {
38888                 proxy = {
38889                     type: proxy
38890                 };
38891             }
38892             proxy = Ext.createByAlias("proxy." + proxy.type, proxy);
38893         }
38894         proxy.setModel(this.self);
38895         this.proxy = proxy;
38896
38897         return proxy;
38898     },
38899
38900     /**
38901      * Returns the configured Proxy for this Model
38902      * @return {Ext.data.proxy.Proxy} The proxy
38903      */
38904     getProxy: function() {
38905         return this.proxy;
38906     },
38907
38908     /**
38909      * Validates the current data against all of its configured {@link #validations} and returns an
38910      * {@link Ext.data.Errors Errors} object
38911      * @return {Ext.data.Errors} The errors object
38912      */
38913     validate: function() {
38914         var errors      = Ext.create('Ext.data.Errors'),
38915             validations = this.validations,
38916             validators  = Ext.data.validations,
38917             length, validation, field, valid, type, i;
38918
38919         if (validations) {
38920             length = validations.length;
38921
38922             for (i = 0; i < length; i++) {
38923                 validation = validations[i];
38924                 field = validation.field || validation.name;
38925                 type  = validation.type;
38926                 valid = validators[type](validation, this.get(field));
38927
38928                 if (!valid) {
38929                     errors.add({
38930                         field  : field,
38931                         message: validation.message || validators[type + 'Message']
38932                     });
38933                 }
38934             }
38935         }
38936
38937         return errors;
38938     },
38939
38940     /**
38941      * Checks if the model is valid. See {@link #validate}.
38942      * @return {Boolean} True if the model is valid.
38943      */
38944     isValid: function(){
38945         return this.validate().isValid();
38946     },
38947
38948     /**
38949      * Saves the model instance using the configured proxy
38950      * @param {Object} options Options to pass to the proxy
38951      * @return {Ext.data.Model} The Model instance
38952      */
38953     save: function(options) {
38954         options = Ext.apply({}, options);
38955
38956         var me     = this,
38957             action = me.phantom ? 'create' : 'update',
38958             record = null,
38959             scope  = options.scope || me,
38960             operation,
38961             callback;
38962
38963         Ext.apply(options, {
38964             records: [me],
38965             action : action
38966         });
38967
38968         operation = Ext.create('Ext.data.Operation', options);
38969
38970         callback = function(operation) {
38971             if (operation.wasSuccessful()) {
38972                 record = operation.getRecords()[0];
38973                 //we need to make sure we've set the updated data here. Ideally this will be redundant once the
38974                 //ModelCache is in place
38975                 me.set(record.data);
38976                 record.dirty = false;
38977
38978                 Ext.callback(options.success, scope, [record, operation]);
38979             } else {
38980                 Ext.callback(options.failure, scope, [record, operation]);
38981             }
38982
38983             Ext.callback(options.callback, scope, [record, operation]);
38984         };
38985
38986         me.getProxy()[action](operation, callback, me);
38987
38988         return me;
38989     },
38990
38991     /**
38992      * Destroys the model using the configured proxy
38993      * @param {Object} options Options to pass to the proxy
38994      * @return {Ext.data.Model} The Model instance
38995      */
38996     destroy: function(options){
38997         options = Ext.apply({}, options);
38998
38999         var me     = this,
39000             record = null,
39001             scope  = options.scope || me,
39002             operation,
39003             callback;
39004
39005         Ext.apply(options, {
39006             records: [me],
39007             action : 'destroy'
39008         });
39009
39010         operation = Ext.create('Ext.data.Operation', options);
39011         callback = function(operation) {
39012             if (operation.wasSuccessful()) {
39013                 Ext.callback(options.success, scope, [record, operation]);
39014             } else {
39015                 Ext.callback(options.failure, scope, [record, operation]);
39016             }
39017             Ext.callback(options.callback, scope, [record, operation]);
39018         };
39019
39020         me.getProxy().destroy(operation, callback, me);
39021         return me;
39022     },
39023
39024     /**
39025      * Returns the unique ID allocated to this model instance as defined by {@link #idProperty}
39026      * @return {Number} The id
39027      */
39028     getId: function() {
39029         return this.get(this.idProperty);
39030     },
39031
39032     /**
39033      * Sets the model instance's id field to the given id
39034      * @param {Number} id The new id
39035      */
39036     setId: function(id) {
39037         this.set(this.idProperty, id);
39038     },
39039
39040     /**
39041      * Tells this model instance that it has been added to a store
39042      * @param {Ext.data.Store} store The store that the model has been added to
39043      */
39044     join : function(store) {
39045         /**
39046          * The {@link Ext.data.Store} to which this Record belongs.
39047          * @property store
39048          * @type {Ext.data.Store}
39049          */
39050         this.store = store;
39051     },
39052
39053     /**
39054      * Tells this model instance that it has been removed from the store
39055      */
39056     unjoin: function() {
39057         delete this.store;
39058     },
39059
39060     /**
39061      * @private
39062      * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
39063      * afterEdit method is called
39064      */
39065     afterEdit : function() {
39066         this.callStore('afterEdit');
39067     },
39068
39069     /**
39070      * @private
39071      * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
39072      * afterReject method is called
39073      */
39074     afterReject : function() {
39075         this.callStore("afterReject");
39076     },
39077
39078     /**
39079      * @private
39080      * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
39081      * afterCommit method is called
39082      */
39083     afterCommit: function() {
39084         this.callStore('afterCommit');
39085     },
39086
39087     /**
39088      * @private
39089      * Helper function used by afterEdit, afterReject and afterCommit. Calls the given method on the
39090      * {@link Ext.data.Store store} that this instance has {@link #join joined}, if any. The store function
39091      * will always be called with the model instance as its single argument.
39092      * @param {String} fn The function to call on the store
39093      */
39094     callStore: function(fn) {
39095         var store = this.store;
39096
39097         if (store !== undefined && typeof store[fn] == "function") {
39098             store[fn](this);
39099         }
39100     },
39101
39102     /**
39103      * Gets all of the data from this Models *loaded* associations.
39104      * It does this recursively - for example if we have a User which
39105      * hasMany Orders, and each Order hasMany OrderItems, it will return an object like this:
39106      * {
39107      *     orders: [
39108      *         {
39109      *             id: 123,
39110      *             status: 'shipped',
39111      *             orderItems: [
39112      *                 ...
39113      *             ]
39114      *         }
39115      *     ]
39116      * }
39117      * @return {Object} The nested data set for the Model's loaded associations
39118      */
39119     getAssociatedData: function(){
39120         return this.prepareAssociatedData(this, [], null);
39121     },
39122
39123     /**
39124      * @private
39125      * This complex-looking method takes a given Model instance and returns an object containing all data from
39126      * all of that Model's *loaded* associations. See (@link #getAssociatedData}
39127      * @param {Ext.data.Model} record The Model instance
39128      * @param {Array} ids PRIVATE. The set of Model instance internalIds that have already been loaded
39129      * @param {String} associationType (optional) The name of the type of association to limit to.
39130      * @return {Object} The nested data set for the Model's loaded associations
39131      */
39132     prepareAssociatedData: function(record, ids, associationType) {
39133         //we keep track of all of the internalIds of the models that we have loaded so far in here
39134         var associations     = record.associations.items,
39135             associationCount = associations.length,
39136             associationData  = {},
39137             associatedStore, associatedName, associatedRecords, associatedRecord,
39138             associatedRecordCount, association, id, i, j, type, allow;
39139
39140         for (i = 0; i < associationCount; i++) {
39141             association = associations[i];
39142             type = association.type;
39143             allow = true;
39144             if (associationType) {
39145                 allow = type == associationType;
39146             }
39147             if (allow && type == 'hasMany') {
39148
39149                 //this is the hasMany store filled with the associated data
39150                 associatedStore = record[association.storeName];
39151
39152                 //we will use this to contain each associated record's data
39153                 associationData[association.name] = [];
39154
39155                 //if it's loaded, put it into the association data
39156                 if (associatedStore && associatedStore.data.length > 0) {
39157                     associatedRecords = associatedStore.data.items;
39158                     associatedRecordCount = associatedRecords.length;
39159
39160                     //now we're finally iterating over the records in the association. We do this recursively
39161                     for (j = 0; j < associatedRecordCount; j++) {
39162                         associatedRecord = associatedRecords[j];
39163                         // Use the id, since it is prefixed with the model name, guaranteed to be unique
39164                         id = associatedRecord.id;
39165
39166                         //when we load the associations for a specific model instance we add it to the set of loaded ids so that
39167                         //we don't load it twice. If we don't do this, we can fall into endless recursive loading failures.
39168                         if (Ext.Array.indexOf(ids, id) == -1) {
39169                             ids.push(id);
39170
39171                             associationData[association.name][j] = associatedRecord.data;
39172                             Ext.apply(associationData[association.name][j], this.prepareAssociatedData(associatedRecord, ids, type));
39173                         }
39174                     }
39175                 }
39176             } else if (allow && type == 'belongsTo') {
39177                 associatedRecord = record[association.instanceName];
39178                 if (associatedRecord !== undefined) {
39179                     id = associatedRecord.id;
39180                     if (Ext.Array.indexOf(ids, id) == -1) {
39181                         ids.push(id);
39182                         associationData[association.name] = associatedRecord.data;
39183                         Ext.apply(associationData[association.name], this.prepareAssociatedData(associatedRecord, ids, type));
39184                     }
39185                 }
39186             }
39187         }
39188
39189         return associationData;
39190     }
39191 });
39192
39193 /**
39194  * @class Ext.Component
39195  * @extends Ext.AbstractComponent
39196  * <p>Base class for all Ext components.  All subclasses of Component may participate in the automated
39197  * Ext component lifecycle of creation, rendering and destruction which is provided by the {@link Ext.container.Container Container} class.
39198  * Components may be added to a Container through the {@link Ext.container.Container#items items} config option at the time the Container is created,
39199  * or they may be added dynamically via the {@link Ext.container.Container#add add} method.</p>
39200  * <p>The Component base class has built-in support for basic hide/show and enable/disable and size control behavior.</p>
39201  * <p>All Components are registered with the {@link Ext.ComponentManager} on construction so that they can be referenced at any time via
39202  * {@link Ext#getCmp Ext.getCmp}, passing the {@link #id}.</p>
39203  * <p>All user-developed visual widgets that are required to participate in automated lifecycle and size management should subclass Component.</p>
39204  * <p>See the <a href="http://sencha.com/learn/Tutorial:Creating_new_UI_controls">Creating new UI controls</a> tutorial for details on how
39205  * and to either extend or augment ExtJs base classes to create custom Components.</p>
39206  * <p>Every component has a specific xtype, which is its Ext-specific type name, along with methods for checking the
39207  * xtype like {@link #getXType} and {@link #isXType}. This is the list of all valid xtypes:</p>
39208  * <pre>
39209 xtype            Class
39210 -------------    ------------------
39211 button           {@link Ext.button.Button}
39212 buttongroup      {@link Ext.container.ButtonGroup}
39213 colorpalette     {@link Ext.picker.Color}
39214 component        {@link Ext.Component}
39215 container        {@link Ext.container.Container}
39216 cycle            {@link Ext.button.Cycle}
39217 dataview         {@link Ext.view.View}
39218 datepicker       {@link Ext.picker.Date}
39219 editor           {@link Ext.Editor}
39220 editorgrid       {@link Ext.grid.plugin.Editing}
39221 grid             {@link Ext.grid.Panel}
39222 multislider      {@link Ext.slider.Multi}
39223 panel            {@link Ext.panel.Panel}
39224 progress         {@link Ext.ProgressBar}
39225 slider           {@link Ext.slider.Single}
39226 spacer           {@link Ext.toolbar.Spacer}
39227 splitbutton      {@link Ext.button.Split}
39228 tabpanel         {@link Ext.tab.Panel}
39229 treepanel        {@link Ext.tree.Panel}
39230 viewport         {@link Ext.container.Viewport}
39231 window           {@link Ext.window.Window}
39232
39233 Toolbar components
39234 ---------------------------------------
39235 paging           {@link Ext.toolbar.Paging}
39236 toolbar          {@link Ext.toolbar.Toolbar}
39237 tbfill           {@link Ext.toolbar.Fill}
39238 tbitem           {@link Ext.toolbar.Item}
39239 tbseparator      {@link Ext.toolbar.Separator}
39240 tbspacer         {@link Ext.toolbar.Spacer}
39241 tbtext           {@link Ext.toolbar.TextItem}
39242
39243 Menu components
39244 ---------------------------------------
39245 menu             {@link Ext.menu.Menu}
39246 menucheckitem    {@link Ext.menu.CheckItem}
39247 menuitem         {@link Ext.menu.Item}
39248 menuseparator    {@link Ext.menu.Separator}
39249 menutextitem     {@link Ext.menu.Item}
39250
39251 Form components
39252 ---------------------------------------
39253 form             {@link Ext.form.Panel}
39254 checkbox         {@link Ext.form.field.Checkbox}
39255 combo            {@link Ext.form.field.ComboBox}
39256 datefield        {@link Ext.form.field.Date}
39257 displayfield     {@link Ext.form.field.Display}
39258 field            {@link Ext.form.field.Base}
39259 fieldset         {@link Ext.form.FieldSet}
39260 hidden           {@link Ext.form.field.Hidden}
39261 htmleditor       {@link Ext.form.field.HtmlEditor}
39262 label            {@link Ext.form.Label}
39263 numberfield      {@link Ext.form.field.Number}
39264 radio            {@link Ext.form.field.Radio}
39265 radiogroup       {@link Ext.form.RadioGroup}
39266 textarea         {@link Ext.form.field.TextArea}
39267 textfield        {@link Ext.form.field.Text}
39268 timefield        {@link Ext.form.field.Time}
39269 trigger          {@link Ext.form.field.Trigger}
39270
39271 Chart components
39272 ---------------------------------------
39273 chart            {@link Ext.chart.Chart}
39274 barchart         {@link Ext.chart.series.Bar}
39275 columnchart      {@link Ext.chart.series.Column}
39276 linechart        {@link Ext.chart.series.Line}
39277 piechart         {@link Ext.chart.series.Pie}
39278
39279 </pre><p>
39280  * It should not usually be necessary to instantiate a Component because there are provided subclasses which implement specialized Component
39281  * use cases which over most application needs. However it is possible to instantiate a base Component, and it will be renderable,
39282  * or will particpate in layouts as the child item of a Container:
39283 {@img Ext.Component/Ext.Component.png Ext.Component component}
39284 <pre><code>
39285     Ext.create('Ext.Component', {
39286         html: 'Hello world!',
39287         width: 300,
39288         height: 200,
39289         padding: 20,
39290         style: {
39291             color: '#FFFFFF',
39292             backgroundColor:'#000000'
39293         },
39294         renderTo: Ext.getBody()
39295     });
39296 </code></pre>
39297  *</p>
39298  *<p>The Component above creates its encapsulating <code>div</code> upon render, and use the configured HTML as content. More complex
39299  * internal structure may be created using the {@link #renderTpl} configuration, although to display database-derived mass
39300  * data, it is recommended that an ExtJS data-backed Component such as a {Ext.view.DataView DataView}, or {Ext.grid.Panel GridPanel},
39301  * or {@link Ext.tree.Panel TreePanel} be used.</p>
39302  * @constructor
39303  * @param {Ext.core.Element/String/Object} config The configuration options may be specified as either:
39304  * <div class="mdetail-params"><ul>
39305  * <li><b>an element</b> :
39306  * <p class="sub-desc">it is set as the internal element and its id used as the component id</p></li>
39307  * <li><b>a string</b> :
39308  * <p class="sub-desc">it is assumed to be the id of an existing element and is used as the component id</p></li>
39309  * <li><b>anything else</b> :
39310  * <p class="sub-desc">it is assumed to be a standard config object and is applied to the component</p></li>
39311  * </ul></div>
39312  */
39313
39314 Ext.define('Ext.Component', {
39315
39316     /* Begin Definitions */
39317
39318     alias: ['widget.component', 'widget.box'],
39319
39320     extend: 'Ext.AbstractComponent',
39321
39322     requires: [
39323         'Ext.util.DelayedTask'
39324     ],
39325
39326     uses: [
39327         'Ext.Layer',
39328         'Ext.resizer.Resizer',
39329         'Ext.util.ComponentDragger'
39330     ],
39331
39332     mixins: {
39333         floating: 'Ext.util.Floating'
39334     },
39335
39336     statics: {
39337         // Collapse/expand directions
39338         DIRECTION_TOP: 'top',
39339         DIRECTION_RIGHT: 'right',
39340         DIRECTION_BOTTOM: 'bottom',
39341         DIRECTION_LEFT: 'left'
39342     },
39343
39344     /* End Definitions */
39345
39346     /**
39347      * @cfg {Mixed} resizable
39348      * <p>Specify as <code>true</code> to apply a {@link Ext.resizer.Resizer Resizer} to this Component
39349      * after rendering.</p>
39350      * <p>May also be specified as a config object to be passed to the constructor of {@link Ext.resizer.Resizer Resizer}
39351      * to override any defaults. By default the Component passes its minimum and maximum size, and uses
39352      * <code>{@link Ext.resizer.Resizer#dynamic}: false</code></p>
39353      */
39354
39355     /**
39356      * @cfg {String} resizeHandles
39357      * A valid {@link Ext.resizer.Resizer} handles config string (defaults to 'all').  Only applies when resizable = true.
39358      */
39359     resizeHandles: 'all',
39360
39361     /**
39362      * @cfg {Boolean} autoScroll
39363      * <code>true</code> to use overflow:'auto' on the components layout element and show scroll bars automatically when
39364      * necessary, <code>false</code> to clip any overflowing content (defaults to <code>false</code>).
39365      */
39366
39367     /**
39368      * @cfg {Boolean} floating
39369      * <p>Specify as true to float the Component outside of the document flow using CSS absolute positioning.</p>
39370      * <p>Components such as {@link Ext.window.Window Window}s and {@link Ext.menu.Menu Menu}s are floating
39371      * by default.</p>
39372      * <p>Floating Components that are programatically {@link Ext.Component#render rendered} will register themselves with the global
39373      * {@link Ext.WindowManager ZIndexManager}</p>
39374      * <h3 class="pa">Floating Components as child items of a Container</h3>
39375      * <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
39376      * examining the ownerCt chain.</p>
39377      * <p>When configured as floating, Components acquire, at render time, a {@link Ext.ZIndexManager ZIndexManager} which manages a stack
39378      * of related floating Components. The ZIndexManager brings a single floating Component to the top of its stack when
39379      * the Component's {@link #toFront} method is called.</p>
39380      * <p>The ZIndexManager is found by traversing up the {@link #ownerCt} chain to find an ancestor which itself is floating. This is so that
39381      * descendant floating Components of floating <i>Containers</i> (Such as a ComboBox dropdown within a Window) can have its zIndex managed relative
39382      * to any siblings, but always <b>above</b> that floating ancestor Container.</p>
39383      * <p>If no floating ancestor is found, a floating Component registers itself with the default {@link Ext.WindowManager ZIndexManager}.</p>
39384      * <p>Floating components <i>do not participate in the Container's layout</i>. Because of this, they are not rendered until you explicitly
39385      * {@link #show} them.</p>
39386      * <p>After rendering, the ownerCt reference is deleted, and the {@link #floatParent} property is set to the found floating ancestor Container.
39387      * If no floating ancestor Container was found the {@link #floatParent} property will not be set.</p>
39388      */
39389     floating: false,
39390
39391     /**
39392      * @cfg {Boolean} toFrontOnShow
39393      * <p>True to automatically call {@link #toFront} when the {@link #show} method is called
39394      * on an already visible, floating component (default is <code>true</code>).</p>
39395      */
39396     toFrontOnShow: true,
39397
39398     /**
39399      * <p>Optional. Only present for {@link #floating} Components after they have been rendered.</p>
39400      * <p>A reference to the ZIndexManager which is managing this Component's z-index.</p>
39401      * <p>The {@link Ext.ZIndexManager ZIndexManager} maintains a stack of floating Component z-indices, and also provides a single modal
39402      * mask which is insert just beneath the topmost visible modal floating Component.</p>
39403      * <p>Floating Components may be {@link #toFront brought to the front} or {@link #toBack sent to the back} of the z-index stack.</p>
39404      * <p>This defaults to the global {@link Ext.WindowManager ZIndexManager} for floating Components that are programatically
39405      * {@link Ext.Component#render rendered}.</p>
39406      * <p>For {@link #floating} Components which are added to a Container, the ZIndexManager is acquired from the first ancestor Container found
39407      * which is floating, or if not found the global {@link Ext.WindowManager ZIndexManager} is used.</p>
39408      * <p>See {@link #floating} and {@link #floatParent}</p>
39409      * @property zIndexManager
39410      * @type Ext.ZIndexManager
39411      */
39412
39413      /**
39414       * <p>Optional. Only present for {@link #floating} Components which were inserted as descendant items of floating Containers.</p>
39415       * <p>Floating Components that are programatically {@link Ext.Component#render rendered} will not have a <code>floatParent</code> property.</p>
39416       * <p>For {@link #floating} Components which are child items of a Container, the floatParent will be the floating ancestor Container which is
39417       * responsible for the base z-index value of all its floating descendants. It provides a {@link Ext.ZIndexManager ZIndexManager} which provides
39418       * z-indexing services for all its descendant floating Components.</p>
39419       * <p>For example, the dropdown {@link Ext.view.BoundList BoundList} of a ComboBox which is in a Window will have the Window as its
39420       * <code>floatParent</code></p>
39421       * <p>See {@link #floating} and {@link #zIndexManager}</p>
39422       * @property floatParent
39423       * @type Ext.Container
39424       */
39425
39426     /**
39427      * @cfg {Mixed} draggable
39428      * <p>Specify as true to make a {@link #floating} Component draggable using the Component's encapsulating element as the drag handle.</p>
39429      * <p>This may also be specified as a config object for the {@link Ext.util.ComponentDragger ComponentDragger} which is instantiated to perform dragging.</p>
39430      * <p>For example to create a Component which may only be dragged around using a certain internal element as the drag handle,
39431      * use the delegate option:</p>
39432      * <code><pre>
39433 new Ext.Component({
39434     constrain: true,
39435     floating:true,
39436     style: {
39437         backgroundColor: '#fff',
39438         border: '1px solid black'
39439     },
39440     html: '&lt;h1 style="cursor:move"&gt;The title&lt;/h1&gt;&lt;p&gt;The content&lt;/p&gt;',
39441     draggable: {
39442         delegate: 'h1'
39443     }
39444 }).show();
39445 </pre></code>
39446      */
39447
39448     /**
39449      * @cfg {Boolean} maintainFlex
39450      * <p><b>Only valid when a sibling element of a {@link Ext.resizer.Splitter Splitter} within a {@link Ext.layout.container.VBox VBox} or
39451      * {@link Ext.layout.container.HBox HBox} layout.</b></p>
39452      * <p>Specifies that if an immediate sibling Splitter is moved, the Component on the <i>other</i> side is resized, and this
39453      * Component maintains its configured {@link Ext.layout.container.Box#flex flex} value.</p>
39454      */
39455
39456     hideMode: 'display',
39457     // Deprecate 5.0
39458     hideParent: false,
39459
39460     ariaRole: 'presentation',
39461
39462     bubbleEvents: [],
39463
39464     actionMode: 'el',
39465     monPropRe: /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate)$/,
39466
39467     //renderTpl: new Ext.XTemplate(
39468     //    '<div id="{id}" class="{baseCls} {cls} {cmpCls}<tpl if="typeof ui !== \'undefined\'"> {uiBase}-{ui}</tpl>"<tpl if="typeof style !== \'undefined\'"> style="{style}"</tpl>></div>', {
39469     //        compiled: true,
39470     //        disableFormats: true
39471     //    }
39472     //),
39473     constructor: function(config) {
39474         config = config || {};
39475         if (config.initialConfig) {
39476
39477             // Being initialized from an Ext.Action instance...
39478             if (config.isAction) {
39479                 this.baseAction = config;
39480             }
39481             config = config.initialConfig;
39482             // component cloning / action set up
39483         }
39484         else if (config.tagName || config.dom || Ext.isString(config)) {
39485             // element object
39486             config = {
39487                 applyTo: config,
39488                 id: config.id || config
39489             };
39490         }
39491
39492         this.callParent([config]);
39493
39494         // If we were configured from an instance of Ext.Action, (or configured with a baseAction option),
39495         // register this Component as one of its items
39496         if (this.baseAction){
39497             this.baseAction.addComponent(this);
39498         }
39499     },
39500
39501     initComponent: function() {
39502         var me = this;
39503
39504         if (me.listeners) {
39505             me.on(me.listeners);
39506             delete me.listeners;
39507         }
39508         me.enableBubble(me.bubbleEvents);
39509         me.mons = [];
39510     },
39511
39512     // private
39513     afterRender: function() {
39514         var me = this,
39515             resizable = me.resizable;
39516
39517         if (me.floating) {
39518             me.makeFloating(me.floating);
39519         } else {
39520             me.el.setVisibilityMode(Ext.core.Element[me.hideMode.toUpperCase()]);
39521         }
39522
39523         me.setAutoScroll(me.autoScroll);
39524         me.callParent();
39525
39526         if (!(me.x && me.y) && (me.pageX || me.pageY)) {
39527             me.setPagePosition(me.pageX, me.pageY);
39528         }
39529
39530         if (resizable) {
39531             me.initResizable(resizable);
39532         }
39533
39534         if (me.draggable) {
39535             me.initDraggable();
39536         }
39537
39538         me.initAria();
39539     },
39540
39541     initAria: function() {
39542         var actionEl = this.getActionEl(),
39543             role = this.ariaRole;
39544         if (role) {
39545             actionEl.dom.setAttribute('role', role);
39546         }
39547     },
39548
39549     /**
39550      * Sets the overflow on the content element of the component.
39551      * @param {Boolean} scroll True to allow the Component to auto scroll.
39552      * @return {Ext.Component} this
39553      */
39554     setAutoScroll : function(scroll){
39555         var me = this,
39556             targetEl;
39557         scroll = !!scroll;
39558         if (me.rendered) {
39559             targetEl = me.getTargetEl();
39560             targetEl.setStyle('overflow', scroll ? 'auto' : '');
39561             if (scroll && (Ext.isIE6 || Ext.isIE7)) {
39562                 // The scrollable container element must be non-statically positioned or IE6/7 will make
39563                 // positioned children stay in place rather than scrolling with the rest of the content
39564                 targetEl.position();
39565             }
39566         }
39567         me.autoScroll = scroll;
39568         return me;
39569     },
39570
39571     // private
39572     makeFloating : function(cfg){
39573         this.mixins.floating.constructor.call(this, cfg);
39574     },
39575
39576     initResizable: function(resizable) {
39577         resizable = Ext.apply({
39578             target: this,
39579             dynamic: false,
39580             constrainTo: this.constrainTo,
39581             handles: this.resizeHandles
39582         }, resizable);
39583         resizable.target = this;
39584         this.resizer = Ext.create('Ext.resizer.Resizer', resizable);
39585     },
39586
39587     getDragEl: function() {
39588         return this.el;
39589     },
39590
39591     initDraggable: function() {
39592         var me = this,
39593             ddConfig = Ext.applyIf({
39594                 el: this.getDragEl(),
39595                 constrainTo: me.constrainTo || (me.floatParent ? me.floatParent.getTargetEl() : me.el.dom.parentNode)
39596             }, this.draggable);
39597
39598         // Add extra configs if Component is specified to be constrained
39599         if (me.constrain || me.constrainDelegate) {
39600             ddConfig.constrain = me.constrain;
39601             ddConfig.constrainDelegate = me.constrainDelegate;
39602         }
39603
39604         this.dd = Ext.create('Ext.util.ComponentDragger', this, ddConfig);
39605     },
39606
39607     /**
39608      * Sets the left and top of the component.  To set the page XY position instead, use {@link #setPagePosition}.
39609      * This method fires the {@link #move} event.
39610      * @param {Number} left The new left
39611      * @param {Number} top The new top
39612      * @param {Mixed} animate If true, the Component is <i>animated</i> into its new position. You may also pass an animation configuration.
39613      * @return {Ext.Component} this
39614      */
39615     setPosition: function(x, y, animate) {
39616         var me = this,
39617             el = me.el,
39618             to = {},
39619             adj, adjX, adjY, xIsNumber, yIsNumber;
39620
39621         if (Ext.isArray(x)) {
39622             animate = y;
39623             y = x[1];
39624             x = x[0];
39625         }
39626         me.x = x;
39627         me.y = y;
39628
39629         if (!me.rendered) {
39630             return me;
39631         }
39632
39633         adj = me.adjustPosition(x, y);
39634         adjX = adj.x;
39635         adjY = adj.y;
39636         xIsNumber = Ext.isNumber(adjX);
39637         yIsNumber = Ext.isNumber(adjY);
39638
39639         if (xIsNumber || yIsNumber) {
39640             if (animate) {
39641                 if (xIsNumber) {
39642                     to.left = adjX;
39643                 }
39644                 if (yIsNumber) {
39645                     to.top = adjY;
39646                 }
39647
39648                 me.stopAnimation();
39649                 me.animate(Ext.apply({
39650                     duration: 1000,
39651                     listeners: {
39652                         afteranimate: Ext.Function.bind(me.afterSetPosition, me, [adjX, adjY])
39653                     },
39654                     to: to
39655                 }, animate));
39656             }
39657             else {
39658                 if (!xIsNumber) {
39659                     el.setTop(adjY);
39660                 }
39661                 else if (!yIsNumber) {
39662                     el.setLeft(adjX);
39663                 }
39664                 else {
39665                     el.setLeftTop(adjX, adjY);
39666                 }
39667                 me.afterSetPosition(adjX, adjY);
39668             }
39669         }
39670         return me;
39671     },
39672
39673     /**
39674      * @private Template method called after a Component has been positioned.
39675      */
39676     afterSetPosition: function(ax, ay) {
39677         this.onPosition(ax, ay);
39678         this.fireEvent('move', this, ax, ay);
39679     },
39680
39681     showAt: function(x, y, animate) {
39682         // A floating Component is positioned relative to its ownerCt if any.
39683         if (this.floating) {
39684             this.setPosition(x, y, animate);
39685         } else {
39686             this.setPagePosition(x, y, animate);
39687         }
39688         this.show();
39689     },
39690
39691     /**
39692      * Sets the page XY position of the component.  To set the left and top instead, use {@link #setPosition}.
39693      * This method fires the {@link #move} event.
39694      * @param {Number} x The new x position
39695      * @param {Number} y The new y position
39696      * @param {Mixed} animate If passed, the Component is <i>animated</i> into its new position. If this parameter
39697      * is a number, it is used as the animation duration in milliseconds.
39698      * @return {Ext.Component} this
39699      */
39700     setPagePosition: function(x, y, animate) {
39701         var me = this,
39702             p;
39703
39704         if (Ext.isArray(x)) {
39705             y = x[1];
39706             x = x[0];
39707         }
39708         me.pageX = x;
39709         me.pageY = y;
39710         if (me.floating && me.floatParent) {
39711             // Floating Components being positioned in their ownerCt have to be made absolute
39712             p = me.floatParent.getTargetEl().getViewRegion();
39713             if (Ext.isNumber(x) && Ext.isNumber(p.left)) {
39714                 x -= p.left;
39715             }
39716             if (Ext.isNumber(y) && Ext.isNumber(p.top)) {
39717                 y -= p.top;
39718             }
39719             me.setPosition(x, y, animate);
39720         }
39721         else {
39722             p = me.el.translatePoints(x, y);
39723             me.setPosition(p.left, p.top, animate);
39724         }
39725         return me;
39726     },
39727
39728     /**
39729      * Gets the current box measurements of the component's underlying element.
39730      * @param {Boolean} local (optional) If true the element's left and top are returned instead of page XY (defaults to false)
39731      * @return {Object} box An object in the format {x, y, width, height}
39732      */
39733     getBox : function(local){
39734         var pos = this.getPosition(local);
39735         var s = this.getSize();
39736         s.x = pos[0];
39737         s.y = pos[1];
39738         return s;
39739     },
39740
39741     /**
39742      * Sets the current box measurements of the component's underlying element.
39743      * @param {Object} box An object in the format {x, y, width, height}
39744      * @return {Ext.Component} this
39745      */
39746     updateBox : function(box){
39747         this.setSize(box.width, box.height);
39748         this.setPagePosition(box.x, box.y);
39749         return this;
39750     },
39751
39752     // Include margins
39753     getOuterSize: function() {
39754         var el = this.el;
39755         return {
39756             width: el.getWidth() + el.getMargin('lr'),
39757             height: el.getHeight() + el.getMargin('tb')
39758         };
39759     },
39760
39761     // private
39762     adjustSize: function(w, h) {
39763         if (this.autoWidth) {
39764             w = 'auto';
39765         }
39766
39767         if (this.autoHeight) {
39768             h = 'auto';
39769         }
39770
39771         return {
39772             width: w,
39773             height: h
39774         };
39775     },
39776
39777     // private
39778     adjustPosition: function(x, y) {
39779
39780         // Floating Components being positioned in their ownerCt have to be made absolute
39781         if (this.floating && this.floatParent) {
39782             var o = this.floatParent.getTargetEl().getViewRegion();
39783             x += o.left;
39784             y += o.top;
39785         }
39786
39787         return {
39788             x: x,
39789             y: y
39790         };
39791     },
39792
39793     /**
39794      * Gets the current XY position of the component's underlying element.
39795      * @param {Boolean} local (optional) If true the element's left and top are returned instead of page XY (defaults to false)
39796      * @return {Array} The XY position of the element (e.g., [100, 200])
39797      */
39798     getPosition: function(local) {
39799         var el = this.el,
39800             xy;
39801
39802         if (local === true) {
39803             return [el.getLeft(true), el.getTop(true)];
39804         }
39805         xy = this.xy || el.getXY();
39806
39807         // Floating Components in an ownerCt have to have their positions made relative
39808         if (this.floating && this.floatParent) {
39809             var o = this.floatParent.getTargetEl().getViewRegion();
39810             xy[0] -= o.left;
39811             xy[1] -= o.top;
39812         }
39813         return xy;
39814     },
39815
39816     // Todo: add in xtype prefix support
39817     getId: function() {
39818         return this.id || (this.id = (this.getXType() || 'ext-comp') + '-' + this.getAutoId());
39819     },
39820
39821     onEnable: function() {
39822         var actionEl = this.getActionEl();
39823         actionEl.dom.removeAttribute('aria-disabled');
39824         actionEl.dom.disabled = false;
39825         this.callParent();
39826     },
39827
39828     onDisable: function() {
39829         var actionEl = this.getActionEl();
39830         actionEl.dom.setAttribute('aria-disabled', true);
39831         actionEl.dom.disabled = true;
39832         this.callParent();
39833     },
39834
39835     /**
39836      * <p>Shows this Component, rendering it first if {@link #autoRender} or {{@link "floating} are <code>true</code>.</p>
39837      * <p>After being shown, a {@link #floating} Component (such as a {@link Ext.window.Window}), is activated it and brought to the front of
39838      * its {@link #ZIndexManager z-index stack}.</p>
39839      * @param {String/Element} animateTarget Optional, and <b>only valid for {@link #floating} Components such as
39840      * {@link Ext.window.Window Window}s or {@link Ext.tip.ToolTip ToolTip}s, or regular Components which have been configured
39841      * with <code>floating: true</code>.</b> The target from which the Component should
39842      * animate from while opening (defaults to null with no animation)
39843      * @param {Function} callback (optional) A callback function to call after the Component is displayed. Only necessary if animation was specified.
39844      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the callback is executed. Defaults to this Component.
39845      * @return {Component} this
39846      */
39847     show: function(animateTarget, cb, scope) {
39848         if (this.rendered && this.isVisible()) {
39849             if (this.toFrontOnShow && this.floating) {
39850                 this.toFront();
39851             }
39852         } else if (this.fireEvent('beforeshow', this) !== false) {
39853             this.hidden = false;
39854
39855             // Render on first show if there is an autoRender config, or if this is a floater (Window, Menu, BoundList etc).
39856             if (!this.rendered && (this.autoRender || this.floating)) {
39857                 this.doAutoRender();
39858             }
39859             if (this.rendered) {
39860                 this.beforeShow();
39861                 this.onShow.apply(this, arguments);
39862
39863                 // Notify any owning Container unless it's suspended.
39864                 // Floating Components do not participate in layouts.
39865                 if (this.ownerCt && !this.floating && !(this.ownerCt.suspendLayout || this.ownerCt.layout.layoutBusy)) {
39866                     this.ownerCt.doLayout();
39867                 }
39868                 this.afterShow.apply(this, arguments);
39869             }
39870         }
39871         return this;
39872     },
39873
39874     beforeShow: Ext.emptyFn,
39875
39876     // Private. Override in subclasses where more complex behaviour is needed.
39877     onShow: function() {
39878         var me = this;
39879
39880         me.el.show();
39881         if (this.floating && this.constrain) {
39882             this.doConstrain();
39883         }
39884         me.callParent(arguments);
39885     },
39886
39887     afterShow: function(animateTarget, cb, scope) {
39888         var me = this,
39889             fromBox,
39890             toBox,
39891             ghostPanel;
39892
39893         // Default to configured animate target if none passed
39894         animateTarget = animateTarget || me.animateTarget;
39895
39896         // Need to be able to ghost the Component
39897         if (!me.ghost) {
39898             animateTarget = null;
39899         }
39900         // If we're animating, kick of an animation of the ghost from the target to the *Element* current box
39901         if (animateTarget) {
39902             animateTarget = animateTarget.el ? animateTarget.el : Ext.get(animateTarget);
39903             toBox = me.el.getBox();
39904             fromBox = animateTarget.getBox();
39905             fromBox.width += 'px';
39906             fromBox.height += 'px';
39907             toBox.width += 'px';
39908             toBox.height += 'px';
39909             me.el.addCls(Ext.baseCSSPrefix + 'hide-offsets');
39910             ghostPanel = me.ghost();
39911             ghostPanel.el.stopAnimation();
39912
39913             ghostPanel.el.animate({
39914                 from: fromBox,
39915                 to: toBox,
39916                 listeners: {
39917                     afteranimate: function() {
39918                         delete ghostPanel.componentLayout.lastComponentSize;
39919                         me.unghost();
39920                         me.el.removeCls(Ext.baseCSSPrefix + 'hide-offsets');
39921                         if (me.floating) {
39922                             me.toFront();
39923                         }
39924                         Ext.callback(cb, scope || me);
39925                     }
39926                 }
39927             });
39928         }
39929         else {
39930             if (me.floating) {
39931                 me.toFront();
39932             }
39933             Ext.callback(cb, scope || me);
39934         }
39935         me.fireEvent('show', me);
39936     },
39937
39938     /**
39939      * Hides this Component, setting it to invisible using the configured {@link #hideMode}.
39940      * @param {String/Element/Component} animateTarget Optional, and <b>only valid for {@link #floating} Components such as
39941      * {@link Ext.window.Window Window}s or {@link Ext.tip.ToolTip ToolTip}s, or regular Components which have been configured
39942      * with <code>floating: true</code>.</b>.
39943      * The target to which the Component should animate while hiding (defaults to null with no animation)
39944      * @param {Function} callback (optional) A callback function to call after the Component is hidden.
39945      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the callback is executed. Defaults to this Component.
39946      * @return {Ext.Component} this
39947      */
39948     hide: function() {
39949
39950         // Clear the flag which is set if a floatParent was hidden while this is visible.
39951         // If a hide operation was subsequently called, that pending show must be hidden.
39952         this.showOnParentShow = false;
39953
39954         if (!(this.rendered && !this.isVisible()) && this.fireEvent('beforehide', this) !== false) {
39955             this.hidden = true;
39956             if (this.rendered) {
39957                 this.onHide.apply(this, arguments);
39958
39959                 // Notify any owning Container unless it's suspended.
39960                 // Floating Components do not participate in layouts.
39961                 if (this.ownerCt && !this.floating && !(this.ownerCt.suspendLayout || this.ownerCt.layout.layoutBusy)) {
39962                     this.ownerCt.doLayout();
39963                 }
39964             }
39965         }
39966         return this;
39967     },
39968
39969     // Possibly animate down to a target element.
39970     onHide: function(animateTarget, cb, scope) {
39971         var me = this,
39972             ghostPanel,
39973             toBox;
39974
39975         // Default to configured animate target if none passed
39976         animateTarget = animateTarget || me.animateTarget;
39977
39978         // Need to be able to ghost the Component
39979         if (!me.ghost) {
39980             animateTarget = null;
39981         }
39982         // If we're animating, kick off an animation of the ghost down to the target
39983         if (animateTarget) {
39984             animateTarget = animateTarget.el ? animateTarget.el : Ext.get(animateTarget);
39985             ghostPanel = me.ghost();
39986             ghostPanel.el.stopAnimation();
39987             toBox = animateTarget.getBox();
39988             toBox.width += 'px';
39989             toBox.height += 'px';
39990             ghostPanel.el.animate({
39991                 to: toBox,
39992                 listeners: {
39993                     afteranimate: function() {
39994                         delete ghostPanel.componentLayout.lastComponentSize;
39995                         ghostPanel.el.hide();
39996                         me.afterHide(cb, scope);
39997                     }
39998                 }
39999             });
40000         }
40001         me.el.hide();
40002         if (!animateTarget) {
40003             me.afterHide(cb, scope);
40004         }
40005     },
40006
40007     afterHide: function(cb, scope) {
40008         Ext.callback(cb, scope || this);
40009         this.fireEvent('hide', this);
40010     },
40011
40012     /**
40013      * @private
40014      * Template method to contribute functionality at destroy time.
40015      */
40016     onDestroy: function() {
40017         var me = this;
40018
40019         // Ensure that any ancillary components are destroyed.
40020         if (me.rendered) {
40021             Ext.destroy(
40022                 me.proxy,
40023                 me.resizer
40024             );
40025             // Different from AbstractComponent
40026             if (me.actionMode == 'container' || me.removeMode == 'container') {
40027                 me.container.remove();
40028             }
40029         }
40030         me.callParent();
40031     },
40032
40033     deleteMembers: function() {
40034         var args = arguments,
40035             len = args.length,
40036             i = 0;
40037         for (; i < len; ++i) {
40038             delete this[args[i]];
40039         }
40040     },
40041
40042     /**
40043      * Try to focus this component.
40044      * @param {Boolean} selectText (optional) If applicable, true to also select the text in this component
40045      * @param {Boolean/Number} delay (optional) Delay the focus this number of milliseconds (true for 10 milliseconds).
40046      * @return {Ext.Component} this
40047      */
40048     focus: function(selectText, delay) {
40049         var me = this,
40050                 focusEl;
40051
40052         if (delay) {
40053             me.focusTask.delay(Ext.isNumber(delay) ? delay: 10, null, me, [selectText, false]);
40054             return me;
40055         }
40056
40057         if (me.rendered && !me.isDestroyed) {
40058             // getFocusEl could return a Component.
40059             focusEl = me.getFocusEl();
40060             focusEl.focus();
40061             if (focusEl.dom && selectText === true) {
40062                 focusEl.dom.select();
40063             }
40064
40065             // Focusing a floating Component brings it to the front of its stack.
40066             // this is performed by its zIndexManager. Pass preventFocus true to avoid recursion.
40067             if (me.floating) {
40068                 me.toFront(true);
40069             }
40070         }
40071         return me;
40072     },
40073
40074     /**
40075      * @private
40076      * Returns the focus holder element associated with this Component. By default, this is the Component's encapsulating
40077      * element. Subclasses which use embedded focusable elements (such as Window and Button) should override this for use
40078      * by the {@link #focus} method.
40079      * @returns {Ext.core.Element} the focus holing element.
40080      */
40081     getFocusEl: function() {
40082         return this.el;
40083     },
40084
40085     // private
40086     blur: function() {
40087         if (this.rendered) {
40088             this.getFocusEl().blur();
40089         }
40090         return this;
40091     },
40092
40093     getEl: function() {
40094         return this.el;
40095     },
40096
40097     // Deprecate 5.0
40098     getResizeEl: function() {
40099         return this.el;
40100     },
40101
40102     // Deprecate 5.0
40103     getPositionEl: function() {
40104         return this.el;
40105     },
40106
40107     // Deprecate 5.0
40108     getActionEl: function() {
40109         return this.el;
40110     },
40111
40112     // Deprecate 5.0
40113     getVisibilityEl: function() {
40114         return this.el;
40115     },
40116
40117     // Deprecate 5.0
40118     onResize: Ext.emptyFn,
40119
40120     // private
40121     getBubbleTarget: function() {
40122         return this.ownerCt;
40123     },
40124
40125     // private
40126     getContentTarget: function() {
40127         return this.el;
40128     },
40129
40130     /**
40131      * Clone the current component using the original config values passed into this instance by default.
40132      * @param {Object} overrides A new config containing any properties to override in the cloned version.
40133      * An id property can be passed on this object, otherwise one will be generated to avoid duplicates.
40134      * @return {Ext.Component} clone The cloned copy of this component
40135      */
40136     cloneConfig: function(overrides) {
40137         overrides = overrides || {};
40138         var id = overrides.id || Ext.id();
40139         var cfg = Ext.applyIf(overrides, this.initialConfig);
40140         cfg.id = id;
40141
40142         var self = Ext.getClass(this);
40143
40144         // prevent dup id
40145         return new self(cfg);
40146     },
40147
40148     /**
40149      * Gets the xtype for this component as registered with {@link Ext.ComponentManager}. For a list of all
40150      * available xtypes, see the {@link Ext.Component} header. Example usage:
40151      * <pre><code>
40152 var t = new Ext.form.field.Text();
40153 alert(t.getXType());  // alerts 'textfield'
40154 </code></pre>
40155      * @return {String} The xtype
40156      */
40157     getXType: function() {
40158         return this.self.xtype;
40159     },
40160
40161     /**
40162      * Find a container above this component at any level by a custom function. If the passed function returns
40163      * true, the container will be returned.
40164      * @param {Function} fn The custom function to call with the arguments (container, this component).
40165      * @return {Ext.container.Container} The first Container for which the custom function returns true
40166      */
40167     findParentBy: function(fn) {
40168         var p;
40169
40170         // Iterate up the ownerCt chain until there's no ownerCt, or we find an ancestor which matches using the selector function.
40171         for (p = this.ownerCt; p && !fn(p, this); p = p.ownerCt);
40172         return p || null;
40173     },
40174
40175     /**
40176      * <p>Find a container above this component at any level by xtype or class</p>
40177      * <p>See also the {@link Ext.Component#up up} method.</p>
40178      * @param {String/Class} xtype The xtype string for a component, or the class of the component directly
40179      * @return {Ext.container.Container} The first Container which matches the given xtype or class
40180      */
40181     findParentByType: function(xtype) {
40182         return Ext.isFunction(xtype) ?
40183             this.findParentBy(function(p) {
40184                 return p.constructor === xtype;
40185             })
40186         :
40187             this.up(xtype);
40188     },
40189
40190     /**
40191      * Bubbles up the component/container heirarchy, calling the specified function with each component. The scope (<i>this</i>) of
40192      * function call will be the scope provided or the current component. The arguments to the function
40193      * will be the args provided or the current component. If the function returns false at any point,
40194      * the bubble is stopped.
40195      * @param {Function} fn The function to call
40196      * @param {Object} scope (optional) The scope of the function (defaults to current node)
40197      * @param {Array} args (optional) The args to call the function with (default to passing the current component)
40198      * @return {Ext.Component} this
40199      */
40200     bubble: function(fn, scope, args) {
40201         var p = this;
40202         while (p) {
40203             if (fn.apply(scope || p, args || [p]) === false) {
40204                 break;
40205             }
40206             p = p.ownerCt;
40207         }
40208         return this;
40209     },
40210
40211     getProxy: function() {
40212         if (!this.proxy) {
40213             this.proxy = this.el.createProxy(Ext.baseCSSPrefix + 'proxy-el', Ext.getBody(), true);
40214         }
40215         return this.proxy;
40216     }
40217
40218 }, function() {
40219
40220     // A single focus delayer for all Components.
40221     this.prototype.focusTask = Ext.create('Ext.util.DelayedTask', this.prototype.focus);
40222
40223 });
40224
40225 /**
40226 * @class Ext.layout.container.Container
40227 * @extends Ext.layout.container.AbstractContainer
40228 * @private
40229 * <p>This class is intended to be extended or created via the <tt><b>{@link Ext.container.Container#layout layout}</b></tt>
40230 * configuration property.  See <tt><b>{@link Ext.container.Container#layout}</b></tt> for additional details.</p>
40231 */
40232 Ext.define('Ext.layout.container.Container', {
40233
40234     /* Begin Definitions */
40235
40236     extend: 'Ext.layout.container.AbstractContainer',
40237     alternateClassName: 'Ext.layout.ContainerLayout',
40238     
40239     /* End Definitions */
40240
40241     layoutItem: function(item, box) {
40242         box = box || {};
40243         if (item.componentLayout.initialized !== true) {
40244             this.setItemSize(item, box.width || item.width || undefined, box.height || item.height || undefined);
40245             // item.doComponentLayout(box.width || item.width || undefined, box.height || item.height || undefined);
40246         }
40247     },
40248
40249     getLayoutTargetSize : function() {
40250         var target = this.getTarget(),
40251             ret;
40252
40253         if (target) {
40254             ret = target.getViewSize();
40255
40256             // IE in will sometimes return a width of 0 on the 1st pass of getViewSize.
40257             // Use getStyleSize to verify the 0 width, the adjustment pass will then work properly
40258             // with getViewSize
40259             if (Ext.isIE && ret.width == 0){
40260                 ret = target.getStyleSize();
40261             }
40262
40263             ret.width -= target.getPadding('lr');
40264             ret.height -= target.getPadding('tb');
40265         }
40266         return ret;
40267     },
40268
40269     beforeLayout: function() {
40270         if (this.owner.beforeLayout(arguments) !== false) {
40271             return this.callParent(arguments);
40272         }
40273         else {
40274             return false;
40275         }
40276     },
40277
40278     afterLayout: function() {
40279         this.owner.afterLayout(arguments);
40280         this.callParent(arguments);
40281     },
40282
40283     /**
40284      * @protected
40285      * Returns all items that are rendered
40286      * @return {Array} All matching items
40287      */
40288     getRenderedItems: function() {
40289         var me = this,
40290             target = me.getTarget(),
40291             items = me.getLayoutItems(),
40292             ln = items.length,
40293             renderedItems = [],
40294             i, item;
40295
40296         for (i = 0; i < ln; i++) {
40297             item = items[i];
40298             if (item.rendered && me.isValidParent(item, target, i)) {
40299                 renderedItems.push(item);
40300             }
40301         }
40302
40303         return renderedItems;
40304     },
40305
40306     /**
40307      * @protected
40308      * Returns all items that are both rendered and visible
40309      * @return {Array} All matching items
40310      */
40311     getVisibleItems: function() {
40312         var target   = this.getTarget(),
40313             items = this.getLayoutItems(),
40314             ln = items.length,
40315             visibleItems = [],
40316             i, item;
40317
40318         for (i = 0; i < ln; i++) {
40319             item = items[i];
40320             if (item.rendered && this.isValidParent(item, target, i) && item.hidden !== true) {
40321                 visibleItems.push(item);
40322             }
40323         }
40324
40325         return visibleItems;
40326     }
40327 });
40328 /**
40329  * @class Ext.layout.container.Auto
40330  * @extends Ext.layout.container.Container
40331  *
40332  * <p>The AutoLayout is the default layout manager delegated by {@link Ext.container.Container} to
40333  * render any child Components when no <tt>{@link Ext.container.Container#layout layout}</tt> is configured into
40334  * a <tt>{@link Ext.container.Container Container}.</tt>.  AutoLayout provides only a passthrough of any layout calls
40335  * to any child containers.</p>
40336  * {@img Ext.layout.container.Auto/Ext.layout.container.Auto.png Ext.layout.container.Auto container layout}
40337  * Example usage:
40338         Ext.create('Ext.Panel', {
40339                 width: 500,
40340                 height: 280,
40341                 title: "AutoLayout Panel",
40342                 layout: 'auto',
40343                 renderTo: document.body,
40344                 items: [{
40345                         xtype: 'panel',
40346                         title: 'Top Inner Panel',
40347                         width: '75%',
40348                         height: 90
40349                 },{
40350                         xtype: 'panel',
40351                         title: 'Bottom Inner Panel',
40352                         width: '75%',
40353                         height: 90
40354                 }]
40355         });
40356  */
40357
40358 Ext.define('Ext.layout.container.Auto', {
40359
40360     /* Begin Definitions */
40361
40362     alias: ['layout.auto', 'layout.autocontainer'],
40363
40364     extend: 'Ext.layout.container.Container',
40365
40366     /* End Definitions */
40367
40368     type: 'autocontainer',
40369
40370     fixedLayout: false,
40371
40372     bindToOwnerCtComponent: true,
40373
40374     // @private
40375     onLayout : function(owner, target) {
40376         var me = this,
40377             items = me.getLayoutItems(),
40378             ln = items.length,
40379             i;
40380
40381         // Ensure the Container is only primed with the clear element if there are child items.
40382         if (ln) {
40383             // Auto layout uses natural HTML flow to arrange the child items.
40384             // To ensure that all browsers (I'm looking at you IE!) add the bottom margin of the last child to the
40385             // containing element height, we create a zero-sized element with style clear:both to force a "new line"
40386             if (!me.clearEl) {
40387                 me.clearEl = me.getRenderTarget().createChild({
40388                     cls: Ext.baseCSSPrefix + 'clear',
40389                     role: 'presentation'
40390                 });
40391             }
40392
40393             // Auto layout allows CSS to size its child items.
40394             for (i = 0; i < ln; i++) {
40395                 me.setItemSize(items[i]);
40396             }
40397         }
40398     }
40399 });
40400 /**
40401  * @class Ext.container.AbstractContainer
40402  * @extends Ext.Component
40403  * <p>An abstract base class which provides shared methods for Containers across the Sencha product line.</p>
40404  * Please refer to sub class's documentation
40405  */
40406 Ext.define('Ext.container.AbstractContainer', {
40407
40408     /* Begin Definitions */
40409
40410     extend: 'Ext.Component',
40411
40412     requires: [
40413         'Ext.util.MixedCollection',
40414         'Ext.layout.container.Auto',
40415         'Ext.ZIndexManager'
40416     ],
40417
40418     /* End Definitions */
40419     /**
40420      * @cfg {String/Object} layout
40421      * <p><b>*Important</b>: In order for child items to be correctly sized and
40422      * positioned, typically a layout manager <b>must</b> be specified through
40423      * the <code>layout</code> configuration option.</p>
40424      * <br><p>The sizing and positioning of child {@link #items} is the responsibility of
40425      * the Container's layout manager which creates and manages the type of layout
40426      * you have in mind.  For example:</p>
40427      * <p>If the {@link #layout} configuration is not explicitly specified for
40428      * a general purpose container (e.g. Container or Panel) the
40429      * {@link Ext.layout.container.Auto default layout manager} will be used
40430      * which does nothing but render child components sequentially into the
40431      * Container (no sizing or positioning will be performed in this situation).</p>
40432      * <br><p><b><code>layout</code></b> may be specified as either as an Object or
40433      * as a String:</p><div><ul class="mdetail-params">
40434      *
40435      * <li><u>Specify as an Object</u></li>
40436      * <div><ul class="mdetail-params">
40437      * <li>Example usage:</li>
40438      * <pre><code>
40439 layout: {
40440     type: 'vbox',
40441     align: 'left'
40442 }
40443        </code></pre>
40444      *
40445      * <li><code><b>type</b></code></li>
40446      * <br/><p>The layout type to be used for this container.  If not specified,
40447      * a default {@link Ext.layout.container.Auto} will be created and used.</p>
40448      * <br/><p>Valid layout <code>type</code> values are:</p>
40449      * <div class="sub-desc"><ul class="mdetail-params">
40450      * <li><code><b>{@link Ext.layout.container.Auto Auto}</b></code> &nbsp;&nbsp;&nbsp; <b>Default</b></li>
40451      * <li><code><b>{@link Ext.layout.container.Card card}</b></code></li>
40452      * <li><code><b>{@link Ext.layout.container.Fit fit}</b></code></li>
40453      * <li><code><b>{@link Ext.layout.container.HBox hbox}</b></code></li>
40454      * <li><code><b>{@link Ext.layout.container.VBox vbox}</b></code></li>
40455      * <li><code><b>{@link Ext.layout.container.Anchor anchor}</b></code></li>
40456      * <li><code><b>{@link Ext.layout.container.Table table}</b></code></li>
40457      * </ul></div>
40458      *
40459      * <li>Layout specific configuration properties</li>
40460      * <br/><p>Additional layout specific configuration properties may also be
40461      * specified. For complete details regarding the valid config options for
40462      * each layout type, see the layout class corresponding to the <code>type</code>
40463      * specified.</p>
40464      *
40465      * </ul></div>
40466      *
40467      * <li><u>Specify as a String</u></li>
40468      * <div><ul class="mdetail-params">
40469      * <li>Example usage:</li>
40470      * <pre><code>
40471 layout: {
40472     type: 'vbox',
40473     padding: '5',
40474     align: 'left'
40475 }
40476        </code></pre>
40477      * <li><code><b>layout</b></code></li>
40478      * <br/><p>The layout <code>type</code> to be used for this container (see list
40479      * of valid layout type values above).</p><br/>
40480      * <br/><p>Additional layout specific configuration properties. For complete
40481      * details regarding the valid config options for each layout type, see the
40482      * layout class corresponding to the <code>layout</code> specified.</p>
40483      * </ul></div></ul></div>
40484      */
40485
40486     /**
40487      * @cfg {String/Number} activeItem
40488      * A string component id or the numeric index of the component that should be initially activated within the
40489      * container's layout on render.  For example, activeItem: 'item-1' or activeItem: 0 (index 0 = the first
40490      * item in the container's collection).  activeItem only applies to layout styles that can display
40491      * items one at a time (like {@link Ext.layout.container.Card} and {@link Ext.layout.container.Fit}).
40492      */
40493     /**
40494      * @cfg {Object/Array} items
40495      * <p>A single item, or an array of child Components to be added to this container</p>
40496      * <p><b>Unless configured with a {@link #layout}, a Container simply renders child Components serially into
40497      * its encapsulating element and performs no sizing or positioning upon them.</b><p>
40498      * <p>Example:</p>
40499      * <pre><code>
40500 // specifying a single item
40501 items: {...},
40502 layout: 'fit',    // The single items is sized to fit
40503
40504 // specifying multiple items
40505 items: [{...}, {...}],
40506 layout: 'hbox', // The items are arranged horizontally
40507        </code></pre>
40508      * <p>Each item may be:</p>
40509      * <ul>
40510      * <li>A {@link Ext.Component Component}</li>
40511      * <li>A Component configuration object</li>
40512      * </ul>
40513      * <p>If a configuration object is specified, the actual type of Component to be
40514      * instantiated my be indicated by using the {@link Ext.Component#xtype xtype} option.</p>
40515      * <p>Every Component class has its own {@link Ext.Component#xtype xtype}.</p>
40516      * <p>If an {@link Ext.Component#xtype xtype} is not explicitly
40517      * specified, the {@link #defaultType} for the Container is used, which by default is usually <code>panel</code>.</p>
40518      * <p><b>Notes</b>:</p>
40519      * <p>Ext uses lazy rendering. Child Components will only be rendered
40520      * should it become necessary. Items are automatically laid out when they are first
40521      * shown (no sizing is done while hidden), or in response to a {@link #doLayout} call.</p>
40522      * <p>Do not specify <code>{@link Ext.panel.Panel#contentEl contentEl}</code> or 
40523      * <code>{@link Ext.panel.Panel#html html}</code> with <code>items</code>.</p>
40524      */
40525     /**
40526      * @cfg {Object|Function} defaults
40527      * <p>This option is a means of applying default settings to all added items whether added through the {@link #items}
40528      * config or via the {@link #add} or {@link #insert} methods.</p>
40529      * <p>If an added item is a config object, and <b>not</b> an instantiated Component, then the default properties are
40530      * unconditionally applied. If the added item <b>is</b> an instantiated Component, then the default properties are
40531      * applied conditionally so as not to override existing properties in the item.</p>
40532      * <p>If the defaults option is specified as a function, then the function will be called using this Container as the
40533      * scope (<code>this</code> reference) and passing the added item as the first parameter. Any resulting object
40534      * from that call is then applied to the item as default properties.</p>
40535      * <p>For example, to automatically apply padding to the body of each of a set of
40536      * contained {@link Ext.panel.Panel} items, you could pass: <code>defaults: {bodyStyle:'padding:15px'}</code>.</p>
40537      * <p>Usage:</p><pre><code>
40538 defaults: {               // defaults are applied to items, not the container
40539     autoScroll:true
40540 },
40541 items: [
40542     {
40543         xtype: 'panel',   // defaults <b>do not</b> have precedence over
40544         id: 'panel1',     // options in config objects, so the defaults
40545         autoScroll: false // will not be applied here, panel1 will be autoScroll:false
40546     },
40547     new Ext.panel.Panel({       // defaults <b>do</b> have precedence over options
40548         id: 'panel2',     // options in components, so the defaults
40549         autoScroll: false // will be applied here, panel2 will be autoScroll:true.
40550     })
40551 ]</code></pre>
40552      */
40553
40554     /** @cfg {Boolean} suspendLayout
40555      * If true, suspend calls to doLayout.  Useful when batching multiple adds to a container and not passing them
40556      * as multiple arguments or an array.
40557      */
40558     suspendLayout : false,
40559
40560     /** @cfg {Boolean} autoDestroy
40561      * If true the container will automatically destroy any contained component that is removed from it, else
40562      * destruction must be handled manually.
40563      * Defaults to true.
40564      */
40565     autoDestroy : true,
40566
40567      /** @cfg {String} defaultType
40568       * <p>The default {@link Ext.Component xtype} of child Components to create in this Container when
40569       * a child item is specified as a raw configuration object, rather than as an instantiated Component.</p>
40570       * <p>Defaults to <code>'panel'</code>.</p>
40571       */
40572     defaultType: 'panel',
40573
40574     isContainer : true,
40575
40576     baseCls: Ext.baseCSSPrefix + 'container',
40577
40578     /**
40579      * @cfg {Array} bubbleEvents
40580      * <p>An array of events that, when fired, should be bubbled to any parent container.
40581      * See {@link Ext.util.Observable#enableBubble}.
40582      * Defaults to <code>['add', 'remove']</code>.
40583      */
40584     bubbleEvents: ['add', 'remove'],
40585     
40586     // @private
40587     initComponent : function(){
40588         var me = this;
40589         me.addEvents(
40590             /**
40591              * @event afterlayout
40592              * Fires when the components in this container are arranged by the associated layout manager.
40593              * @param {Ext.container.Container} this
40594              * @param {ContainerLayout} layout The ContainerLayout implementation for this container
40595              */
40596             'afterlayout',
40597             /**
40598              * @event beforeadd
40599              * Fires before any {@link Ext.Component} is added or inserted into the container.
40600              * A handler can return false to cancel the add.
40601              * @param {Ext.container.Container} this
40602              * @param {Ext.Component} component The component being added
40603              * @param {Number} index The index at which the component will be added to the container's items collection
40604              */
40605             'beforeadd',
40606             /**
40607              * @event beforeremove
40608              * Fires before any {@link Ext.Component} is removed from the container.  A handler can return
40609              * false to cancel the remove.
40610              * @param {Ext.container.Container} this
40611              * @param {Ext.Component} component The component being removed
40612              */
40613             'beforeremove',
40614             /**
40615              * @event add
40616              * @bubbles
40617              * Fires after any {@link Ext.Component} is added or inserted into the container.
40618              * @param {Ext.container.Container} this
40619              * @param {Ext.Component} component The component that was added
40620              * @param {Number} index The index at which the component was added to the container's items collection
40621              */
40622             'add',
40623             /**
40624              * @event remove
40625              * @bubbles
40626              * Fires after any {@link Ext.Component} is removed from the container.
40627              * @param {Ext.container.Container} this
40628              * @param {Ext.Component} component The component that was removed
40629              */
40630             'remove',
40631             /**
40632              * @event beforecardswitch
40633              * Fires before this container switches the active card. This event
40634              * is only available if this container uses a CardLayout. Note that
40635              * TabPanel and Carousel both get a CardLayout by default, so both
40636              * will have this event.
40637              * A handler can return false to cancel the card switch.
40638              * @param {Ext.container.Container} this
40639              * @param {Ext.Component} newCard The card that will be switched to
40640              * @param {Ext.Component} oldCard The card that will be switched from
40641              * @param {Number} index The index of the card that will be switched to
40642              * @param {Boolean} animated True if this cardswitch will be animated
40643              */
40644             'beforecardswitch',
40645             /**
40646              * @event cardswitch
40647              * Fires after this container switches the active card. If the card
40648              * is switched using an animation, this event will fire after the
40649              * animation has finished. This event is only available if this container
40650              * uses a CardLayout. Note that TabPanel and Carousel both get a CardLayout
40651              * by default, so both will have this event.
40652              * @param {Ext.container.Container} this
40653              * @param {Ext.Component} newCard The card that has been switched to
40654              * @param {Ext.Component} oldCard The card that has been switched from
40655              * @param {Number} index The index of the card that has been switched to
40656              * @param {Boolean} animated True if this cardswitch was animated
40657              */
40658             'cardswitch'
40659         );
40660
40661         // layoutOnShow stack
40662         me.layoutOnShow = Ext.create('Ext.util.MixedCollection');
40663         me.callParent();
40664         me.initItems();
40665     },
40666
40667     // @private
40668     initItems : function() {
40669         var me = this,
40670             items = me.items;
40671
40672         /**
40673          * The MixedCollection containing all the child items of this container.
40674          * @property items
40675          * @type Ext.util.MixedCollection
40676          */
40677         me.items = Ext.create('Ext.util.MixedCollection', false, me.getComponentId);
40678
40679         if (items) {
40680             if (!Ext.isArray(items)) {
40681                 items = [items];
40682             }
40683
40684             me.add(items);
40685         }
40686     },
40687
40688     // @private
40689     afterRender : function() {
40690         this.getLayout();
40691         this.callParent();
40692     },
40693
40694     // @private
40695     setLayout : function(layout) {
40696         var currentLayout = this.layout;
40697
40698         if (currentLayout && currentLayout.isLayout && currentLayout != layout) {
40699             currentLayout.setOwner(null);
40700         }
40701
40702         this.layout = layout;
40703         layout.setOwner(this);
40704     },
40705
40706     /**
40707      * Returns the {@link Ext.layout.container.AbstractContainer layout} instance currently associated with this Container.
40708      * If a layout has not been instantiated yet, that is done first
40709      * @return {Ext.layout.container.AbstractContainer} The layout
40710      */
40711     getLayout : function() {
40712         var me = this;
40713         if (!me.layout || !me.layout.isLayout) {
40714             me.setLayout(Ext.layout.Layout.create(me.layout, 'autocontainer'));
40715         }
40716
40717         return me.layout;
40718     },
40719
40720     /**
40721      * Manually force this container's layout to be recalculated.  The framwork uses this internally to refresh layouts
40722      * form most cases.
40723      * @return {Ext.container.Container} this
40724      */
40725     doLayout : function() {
40726         var me = this,
40727             layout = me.getLayout();
40728
40729         if (me.rendered && layout && !me.suspendLayout) {
40730             // If either dimension is being auto-set, then it requires a ComponentLayout to be run.
40731             if ((!Ext.isNumber(me.width) || !Ext.isNumber(me.height)) && me.componentLayout.type !== 'autocomponent') {
40732                 // Only run the ComponentLayout if it is not already in progress
40733                 if (me.componentLayout.layoutBusy !== true) {
40734                     me.doComponentLayout();
40735                     if (me.componentLayout.layoutCancelled === true) {
40736                         layout.layout();
40737                     }
40738                 }
40739             }
40740             // Both dimensions defined, run a ContainerLayout
40741             else {
40742                 // Only run the ContainerLayout if it is not already in progress
40743                 if (layout.layoutBusy !== true) {
40744                     layout.layout();
40745                 }
40746             }
40747         }
40748
40749         return me;
40750     },
40751
40752     // @private
40753     afterLayout : function(layout) {
40754         this.fireEvent('afterlayout', this, layout);
40755     },
40756
40757     // @private
40758     prepareItems : function(items, applyDefaults) {
40759         if (!Ext.isArray(items)) {
40760             items = [items];
40761         }
40762
40763         // Make sure defaults are applied and item is initialized
40764         var i = 0,
40765             len = items.length,
40766             item;
40767
40768         for (; i < len; i++) {
40769             item = items[i];
40770             if (applyDefaults) {
40771                 item = this.applyDefaults(item);
40772             }
40773             items[i] = this.lookupComponent(item);
40774         }
40775         return items;
40776     },
40777
40778     // @private
40779     applyDefaults : function(config) {
40780         var defaults = this.defaults;
40781
40782         if (defaults) {
40783             if (Ext.isFunction(defaults)) {
40784                 defaults = defaults.call(this, config);
40785             }
40786
40787             if (Ext.isString(config)) {
40788                 config = Ext.ComponentManager.get(config);
40789                 Ext.applyIf(config, defaults);
40790             } else if (!config.isComponent) {
40791                 Ext.applyIf(config, defaults);
40792             } else {
40793                 Ext.applyIf(config, defaults);
40794             }
40795         }
40796
40797         return config;
40798     },
40799
40800     // @private
40801     lookupComponent : function(comp) {
40802         return Ext.isString(comp) ? Ext.ComponentManager.get(comp) : this.createComponent(comp);
40803     },
40804
40805     // @private
40806     createComponent : function(config, defaultType) {
40807         // // add in ownerCt at creation time but then immediately
40808         // // remove so that onBeforeAdd can handle it
40809         // var component = Ext.create(Ext.apply({ownerCt: this}, config), defaultType || this.defaultType);
40810         //
40811         // delete component.initialConfig.ownerCt;
40812         // delete component.ownerCt;
40813
40814         return Ext.ComponentManager.create(config, defaultType || this.defaultType);
40815     },
40816
40817     // @private - used as the key lookup function for the items collection
40818     getComponentId : function(comp) {
40819         return comp.getItemId();
40820     },
40821
40822     /**
40823
40824 Adds {@link Ext.Component Component}(s) to this Container.
40825
40826 ##Description:##
40827
40828 - Fires the {@link #beforeadd} event before adding.
40829 - The Container's {@link #defaults default config values} will be applied
40830   accordingly (see `{@link #defaults}` for details).
40831 - Fires the `{@link #add}` event after the component has been added.
40832
40833 ##Notes:##
40834
40835 If the Container is __already rendered__ when `add`
40836 is called, it will render the newly added Component into its content area.
40837
40838 __**If**__ the Container was configured with a size-managing {@link #layout} manager, the Container
40839 will recalculate its internal layout at this time too.
40840
40841 Note that the default layout manager simply renders child Components sequentially into the content area and thereafter performs no sizing.
40842
40843 If adding multiple new child Components, pass them as an array to the `add` method, so that only one layout recalculation is performed.
40844
40845     tb = new {@link Ext.toolbar.Toolbar}({
40846         renderTo: document.body
40847     });  // toolbar is rendered
40848     tb.add([{text:'Button 1'}, {text:'Button 2'}]); // add multiple items. ({@link #defaultType} for {@link Ext.toolbar.Toolbar Toolbar} is 'button')
40849
40850 ##Warning:## 
40851
40852 Components directly managed by the BorderLayout layout manager
40853 may not be removed or added.  See the Notes for {@link Ext.layout.container.Border BorderLayout}
40854 for more details.
40855
40856      * @param {...Object/Array} Component
40857      * Either one or more Components to add or an Array of Components to add.
40858      * See `{@link #items}` for additional information.
40859      *
40860      * @return {Ext.Component/Array} The Components that were added.
40861      * @markdown
40862      */
40863     add : function() {
40864         var me = this,
40865             args = Array.prototype.slice.call(arguments),
40866             hasMultipleArgs,
40867             items,
40868             results = [],
40869             i,
40870             ln,
40871             item,
40872             index = -1,
40873             cmp;
40874
40875         if (typeof args[0] == 'number') {
40876             index = args.shift();
40877         }
40878
40879         hasMultipleArgs = args.length > 1;
40880         if (hasMultipleArgs || Ext.isArray(args[0])) {
40881
40882             items = hasMultipleArgs ? args : args[0];
40883             // Suspend Layouts while we add multiple items to the container
40884             me.suspendLayout = true;
40885             for (i = 0, ln = items.length; i < ln; i++) {
40886                 item = items[i];
40887                 
40888                 if (!item) {
40889                     Ext.Error.raise("Trying to add a null item as a child of Container with itemId/id: " + me.getItemId());
40890                 }
40891                 
40892                 if (index != -1) {
40893                     item = me.add(index + i, item);
40894                 } else {
40895                     item = me.add(item);
40896                 }
40897                 results.push(item);
40898             }
40899             // Resume Layouts now that all items have been added and do a single layout for all the items just added
40900             me.suspendLayout = false;
40901             me.doLayout();
40902             return results;
40903         }
40904
40905         cmp = me.prepareItems(args[0], true)[0];
40906
40907         // Floating Components are not added into the items collection
40908         // But they do get an upward ownerCt link so that they can traverse
40909         // up to their z-index parent.
40910         if (cmp.floating) {
40911             cmp.onAdded(me, index);
40912         } else {
40913             index = (index !== -1) ? index : me.items.length;
40914             if (me.fireEvent('beforeadd', me, cmp, index) !== false && me.onBeforeAdd(cmp) !== false) {
40915                 me.items.insert(index, cmp);
40916                 cmp.onAdded(me, index);
40917                 me.onAdd(cmp, index);
40918                 me.fireEvent('add', me, cmp, index);
40919             }
40920             me.doLayout();
40921         }
40922         return cmp;
40923     },
40924
40925     /**
40926      * @private
40927      * <p>Called by Component#doAutoRender</p>
40928      * <p>Register a Container configured <code>floating: true</code> with this Container's {@link Ext.ZIndexManager ZIndexManager}.</p>
40929      * <p>Components added in ths way will not participate in the layout, but will be rendered
40930      * upon first show in the way that {@link Ext.window.Window Window}s are.</p>
40931      * <p></p>
40932      */
40933     registerFloatingItem: function(cmp) {
40934         var me = this;
40935         if (!me.floatingItems) {
40936             me.floatingItems = Ext.create('Ext.ZIndexManager', me);
40937         }
40938         me.floatingItems.register(cmp);
40939     },
40940
40941     onAdd : Ext.emptyFn,
40942     onRemove : Ext.emptyFn,
40943
40944     /**
40945      * Inserts a Component into this Container at a specified index. Fires the
40946      * {@link #beforeadd} event before inserting, then fires the {@link #add} event after the
40947      * Component has been inserted.
40948      * @param {Number} index The index at which the Component will be inserted
40949      * into the Container's items collection
40950      * @param {Ext.Component} component The child Component to insert.<br><br>
40951      * Ext uses lazy rendering, and will only render the inserted Component should
40952      * it become necessary.<br><br>
40953      * A Component config object may be passed in order to avoid the overhead of
40954      * constructing a real Component object if lazy rendering might mean that the
40955      * inserted Component will not be rendered immediately. To take advantage of
40956      * this 'lazy instantiation', set the {@link Ext.Component#xtype} config
40957      * property to the registered type of the Component wanted.<br><br>
40958      * For a list of all available xtypes, see {@link Ext.Component}.
40959      * @return {Ext.Component} component The Component (or config object) that was
40960      * inserted with the Container's default config values applied.
40961      */
40962     insert : function(index, comp) {
40963         return this.add(index, comp);
40964     },
40965
40966     /**
40967      * Moves a Component within the Container
40968      * @param {Number} fromIdx The index the Component you wish to move is currently at.
40969      * @param {Number} toIdx The new index for the Component.
40970      * @return {Ext.Component} component The Component (or config object) that was moved.
40971      */
40972     move : function(fromIdx, toIdx) {
40973         var items = this.items,
40974             item;
40975         item = items.removeAt(fromIdx);
40976         if (item === false) {
40977             return false;
40978         }
40979         items.insert(toIdx, item);
40980         this.doLayout();
40981         return item;
40982     },
40983
40984     // @private
40985     onBeforeAdd : function(item) {
40986         var me = this;
40987         
40988         if (item.ownerCt) {
40989             item.ownerCt.remove(item, false);
40990         }
40991
40992         if (me.border === false || me.border === 0) {
40993             item.border = (item.border === true);
40994         }
40995     },
40996
40997     /**
40998      * Removes a component from this container.  Fires the {@link #beforeremove} event before removing, then fires
40999      * the {@link #remove} event after the component has been removed.
41000      * @param {Component/String} component The component reference or id to remove.
41001      * @param {Boolean} autoDestroy (optional) True to automatically invoke the removed Component's {@link Ext.Component#destroy} function.
41002      * Defaults to the value of this Container's {@link #autoDestroy} config.
41003      * @return {Ext.Component} component The Component that was removed.
41004      */
41005     remove : function(comp, autoDestroy) {
41006         var me = this,
41007             c = me.getComponent(comp);
41008             if (Ext.isDefined(Ext.global.console) && !c) {
41009                 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.");
41010             }
41011
41012         if (c && me.fireEvent('beforeremove', me, c) !== false) {
41013             me.doRemove(c, autoDestroy);
41014             me.fireEvent('remove', me, c);
41015         }
41016
41017         return c;
41018     },
41019
41020     // @private
41021     doRemove : function(component, autoDestroy) {
41022         var me = this,
41023             layout = me.layout,
41024             hasLayout = layout && me.rendered;
41025
41026         me.items.remove(component);
41027         component.onRemoved();
41028
41029         if (hasLayout) {
41030             layout.onRemove(component);
41031         }
41032
41033         me.onRemove(component, autoDestroy);
41034
41035         if (autoDestroy === true || (autoDestroy !== false && me.autoDestroy)) {
41036             component.destroy();
41037         }
41038
41039         if (hasLayout && !autoDestroy) {
41040             layout.afterRemove(component);
41041         }
41042
41043         if (!me.destroying) {
41044             me.doLayout();
41045         }
41046     },
41047
41048     /**
41049      * Removes all components from this container.
41050      * @param {Boolean} autoDestroy (optional) True to automatically invoke the removed Component's {@link Ext.Component#destroy} function.
41051      * Defaults to the value of this Container's {@link #autoDestroy} config.
41052      * @return {Array} Array of the destroyed components
41053      */
41054     removeAll : function(autoDestroy) {
41055         var me = this,
41056             removeItems = me.items.items.slice(),
41057             items = [],
41058             i = 0,
41059             len = removeItems.length,
41060             item;
41061
41062         // Suspend Layouts while we remove multiple items from the container
41063         me.suspendLayout = true;
41064         for (; i < len; i++) {
41065             item = removeItems[i];
41066             me.remove(item, autoDestroy);
41067
41068             if (item.ownerCt !== me) {
41069                 items.push(item);
41070             }
41071         }
41072
41073         // Resume Layouts now that all items have been removed and do a single layout
41074         me.suspendLayout = false;
41075         me.doLayout();
41076         return items;
41077     },
41078
41079     // Used by ComponentQuery to retrieve all of the items
41080     // which can potentially be considered a child of this Container.
41081     // This should be overriden by components which have child items
41082     // that are not contained in items. For example dockedItems, menu, etc
41083     // IMPORTANT note for maintainers:
41084     //  Items are returned in tree traversal order. Each item is appended to the result array
41085     //  followed by the results of that child's getRefItems call.
41086     //  Floating child items are appended after internal child items.
41087     getRefItems : function(deep) {
41088         var me = this,
41089             items = me.items.items,
41090             len = items.length,
41091             i = 0,
41092             item,
41093             result = [];
41094
41095         for (; i < len; i++) {
41096             item = items[i];
41097             result.push(item);
41098             if (deep && item.getRefItems) {
41099                 result.push.apply(result, item.getRefItems(true));
41100             }
41101         }
41102
41103         // Append floating items to the list.
41104         // These will only be present after they are rendered.
41105         if (me.floatingItems && me.floatingItems.accessList) {
41106             result.push.apply(result, me.floatingItems.accessList);
41107         }
41108
41109         return result;
41110     },
41111
41112     /**
41113      * Cascades down the component/container heirarchy from this component (passed in the first call), calling the specified function with
41114      * each component. The scope (<code>this</code> reference) of the
41115      * function call will be the scope provided or the current component. The arguments to the function
41116      * will be the args provided or the current component. If the function returns false at any point,
41117      * the cascade is stopped on that branch.
41118      * @param {Function} fn The function to call
41119      * @param {Object} scope (optional) The scope of the function (defaults to current component)
41120      * @param {Array} args (optional) The args to call the function with. The current component always passed as the last argument.
41121      * @return {Ext.Container} this
41122      */
41123     cascade : function(fn, scope, origArgs){
41124         var me = this,
41125             cs = me.items ? me.items.items : [],
41126             len = cs.length,
41127             i = 0,
41128             c,
41129             args = origArgs ? origArgs.concat(me) : [me],
41130             componentIndex = args.length - 1;
41131
41132         if (fn.apply(scope || me, args) !== false) {
41133             for(; i < len; i++){
41134                 c = cs[i];
41135                 if (c.cascade) {
41136                     c.cascade(fn, scope, origArgs);
41137                 } else {
41138                     args[componentIndex] = c;
41139                     fn.apply(scope || cs, args);
41140                 }
41141             }
41142         }
41143         return this;
41144     },
41145
41146     /**
41147      * Examines this container's <code>{@link #items}</code> <b>property</b>
41148      * and gets a direct child component of this container.
41149      * @param {String/Number} comp This parameter may be any of the following:
41150      * <div><ul class="mdetail-params">
41151      * <li>a <b><code>String</code></b> : representing the <code>{@link Ext.Component#itemId itemId}</code>
41152      * or <code>{@link Ext.Component#id id}</code> of the child component </li>
41153      * <li>a <b><code>Number</code></b> : representing the position of the child component
41154      * within the <code>{@link #items}</code> <b>property</b></li>
41155      * </ul></div>
41156      * <p>For additional information see {@link Ext.util.MixedCollection#get}.
41157      * @return Ext.Component The component (if found).
41158      */
41159     getComponent : function(comp) {
41160         if (Ext.isObject(comp)) {
41161             comp = comp.getItemId();
41162         }
41163
41164         return this.items.get(comp);
41165     },
41166
41167     /**
41168      * Retrieves all descendant components which match the passed selector.
41169      * Executes an Ext.ComponentQuery.query using this container as its root.
41170      * @param {String} selector Selector complying to an Ext.ComponentQuery selector
41171      * @return {Array} Ext.Component's which matched the selector
41172      */
41173     query : function(selector) {
41174         return Ext.ComponentQuery.query(selector, this);
41175     },
41176
41177     /**
41178      * Retrieves the first direct child of this container which matches the passed selector.
41179      * The passed in selector must comply with an Ext.ComponentQuery selector.
41180      * @param {String} selector An Ext.ComponentQuery selector
41181      * @return Ext.Component
41182      */
41183     child : function(selector) {
41184         return this.query('> ' + selector)[0] || null;
41185     },
41186
41187     /**
41188      * Retrieves the first descendant of this container which matches the passed selector.
41189      * The passed in selector must comply with an Ext.ComponentQuery selector.
41190      * @param {String} selector An Ext.ComponentQuery selector
41191      * @return Ext.Component
41192      */
41193     down : function(selector) {
41194         return this.query(selector)[0] || null;
41195     },
41196
41197     // inherit docs
41198     show : function() {
41199         this.callParent(arguments);
41200         this.performDeferredLayouts();
41201         return this;
41202     },
41203
41204     // Lay out any descendant containers who queued a layout operation during the time this was hidden
41205     // This is also called by Panel after it expands because descendants of a collapsed Panel allso queue any layout ops.
41206     performDeferredLayouts: function() {
41207         var layoutCollection = this.layoutOnShow,
41208             ln = layoutCollection.getCount(),
41209             i = 0,
41210             needsLayout,
41211             item;
41212
41213         for (; i < ln; i++) {
41214             item = layoutCollection.get(i);
41215             needsLayout = item.needsLayout;
41216
41217             if (Ext.isObject(needsLayout)) {
41218                 item.doComponentLayout(needsLayout.width, needsLayout.height, needsLayout.isSetSize, needsLayout.ownerCt);
41219             }
41220         }
41221         layoutCollection.clear();
41222     },    
41223     
41224     //@private
41225     // Enable all immediate children that was previously disabled
41226     onEnable: function() {
41227         Ext.Array.each(this.query('[isFormField]'), function(item) {
41228             if (item.resetDisable) {
41229                 item.enable();
41230                 delete item.resetDisable;             
41231             }
41232         });
41233         this.callParent();
41234     },
41235     
41236     // @private
41237     // Disable all immediate children that was previously disabled
41238     onDisable: function() {
41239         Ext.Array.each(this.query('[isFormField]'), function(item) {
41240             if (item.resetDisable !== false && !item.disabled) {
41241                 item.disable();
41242                 item.resetDisable = true;
41243             }
41244         });
41245         this.callParent();
41246     },
41247
41248     /**
41249      * Occurs before componentLayout is run. Returning false from this method will prevent the containerLayout
41250      * from being executed.
41251      */
41252     beforeLayout: function() {
41253         return true;
41254     },
41255
41256     // @private
41257     beforeDestroy : function() {
41258         var me = this,
41259             items = me.items,
41260             c;
41261
41262         if (items) {
41263             while ((c = items.first())) {
41264                 me.doRemove(c, true);
41265             }
41266         }
41267
41268         Ext.destroy(
41269             me.layout,
41270             me.floatingItems
41271         );
41272         me.callParent();
41273     }
41274 });
41275 /**
41276  * @class Ext.container.Container
41277  * @extends Ext.container.AbstractContainer
41278  * <p>Base class for any {@link Ext.Component} that may contain other Components. Containers handle the
41279  * basic behavior of containing items, namely adding, inserting and removing items.</p>
41280  *
41281  * <p>The most commonly used Container classes are {@link Ext.panel.Panel}, {@link Ext.window.Window} and {@link Ext.tab.Panel}.
41282  * If you do not need the capabilities offered by the aforementioned classes you can create a lightweight
41283  * Container to be encapsulated by an HTML element to your specifications by using the
41284  * <code><b>{@link Ext.Component#autoEl autoEl}</b></code> config option.</p>
41285  *
41286  * {@img Ext.Container/Ext.Container.png Ext.Container component} 
41287  * <p>The code below illustrates how to explicitly create a Container:<pre><code>
41288 // explicitly create a Container
41289 Ext.create('Ext.container.Container', {
41290     layout: {
41291         type: 'hbox'
41292     },
41293     width: 400,
41294     renderTo: Ext.getBody(),
41295     border: 1,
41296     style: {borderColor:'#000000', borderStyle:'solid', borderWidth:'1px'},
41297     defaults: {
41298         labelWidth: 80,
41299         // implicitly create Container by specifying xtype
41300         xtype: 'datefield',
41301         flex: 1,
41302         style: {
41303             padding: '10px'
41304         }
41305     },
41306     items: [{
41307         xtype: 'datefield',
41308         name: 'startDate',
41309         fieldLabel: 'Start date'
41310     },{
41311         xtype: 'datefield',
41312         name: 'endDate',
41313         fieldLabel: 'End date'
41314     }]
41315 });
41316 </code></pre></p>
41317  *
41318  * <p><u><b>Layout</b></u></p>
41319  * <p>Container classes delegate the rendering of child Components to a layout
41320  * manager class which must be configured into the Container using the
41321  * <code><b>{@link #layout}</b></code> configuration property.</p>
41322  * <p>When either specifying child <code>{@link #items}</code> of a Container,
41323  * or dynamically {@link #add adding} Components to a Container, remember to
41324  * consider how you wish the Container to arrange those child elements, and
41325  * whether those child elements need to be sized using one of Ext's built-in
41326  * <b><code>{@link #layout}</code></b> schemes. By default, Containers use the
41327  * {@link Ext.layout.container.Auto Auto} scheme which only
41328  * renders child components, appending them one after the other inside the
41329  * Container, and <b>does not apply any sizing</b> at all.</p>
41330  * <p>A common mistake is when a developer neglects to specify a
41331  * <b><code>{@link #layout}</code></b> (e.g. widgets like GridPanels or
41332  * TreePanels are added to Containers for which no <code><b>{@link #layout}</b></code>
41333  * has been specified). If a Container is left to use the default
41334  * {Ext.layout.container.Auto Auto} scheme, none of its
41335  * child components will be resized, or changed in any way when the Container
41336  * is resized.</p>
41337  * <p>Certain layout managers allow dynamic addition of child components.
41338  * Those that do include {@link Ext.layout.container.Card},
41339  * {@link Ext.layout.container.Anchor}, {@link Ext.layout.container.VBox}, {@link Ext.layout.container.HBox}, and
41340  * {@link Ext.layout.container.Table}. For example:<pre><code>
41341 //  Create the GridPanel.
41342 var myNewGrid = new Ext.grid.Panel({
41343     store: myStore,
41344     headers: myHeaders,
41345     title: 'Results', // the title becomes the title of the tab
41346 });
41347
41348 myTabPanel.add(myNewGrid); // {@link Ext.tab.Panel} implicitly uses {@link Ext.layout.container.Card Card}
41349 myTabPanel.{@link Ext.tab.Panel#setActiveTab setActiveTab}(myNewGrid);
41350  * </code></pre></p>
41351  * <p>The example above adds a newly created GridPanel to a TabPanel. Note that
41352  * a TabPanel uses {@link Ext.layout.container.Card} as its layout manager which
41353  * means all its child items are sized to {@link Ext.layout.container.Fit fit}
41354  * exactly into its client area.
41355  * <p><b><u>Overnesting is a common problem</u></b>.
41356  * An example of overnesting occurs when a GridPanel is added to a TabPanel
41357  * by wrapping the GridPanel <i>inside</i> a wrapping Panel (that has no
41358  * <code><b>{@link #layout}</b></code> specified) and then add that wrapping Panel
41359  * to the TabPanel. The point to realize is that a GridPanel <b>is</b> a
41360  * Component which can be added directly to a Container. If the wrapping Panel
41361  * has no <code><b>{@link #layout}</b></code> configuration, then the overnested
41362  * GridPanel will not be sized as expected.<p>
41363  *
41364  * <p><u><b>Adding via remote configuration</b></u></p>
41365  *
41366  * <p>A server side script can be used to add Components which are generated dynamically on the server.
41367  * An example of adding a GridPanel to a TabPanel where the GridPanel is generated by the server
41368  * based on certain parameters:
41369  * </p><pre><code>
41370 // execute an Ajax request to invoke server side script:
41371 Ext.Ajax.request({
41372     url: 'gen-invoice-grid.php',
41373     // send additional parameters to instruct server script
41374     params: {
41375         startDate: Ext.getCmp('start-date').getValue(),
41376         endDate: Ext.getCmp('end-date').getValue()
41377     },
41378     // process the response object to add it to the TabPanel:
41379     success: function(xhr) {
41380         var newComponent = eval(xhr.responseText); // see discussion below
41381         myTabPanel.add(newComponent); // add the component to the TabPanel
41382         myTabPanel.setActiveTab(newComponent);
41383     },
41384     failure: function() {
41385         Ext.Msg.alert("Grid create failed", "Server communication failure");
41386     }
41387 });
41388 </code></pre>
41389  * <p>The server script needs to return a JSON representation of a configuration object, which, when decoded
41390  * will return a config object with an {@link Ext.Component#xtype xtype}. The server might return the following
41391  * JSON:</p><pre><code>
41392 {
41393     "xtype": 'grid',
41394     "title": 'Invoice Report',
41395     "store": {
41396         "model": 'Invoice',
41397         "proxy": {
41398             "type": 'ajax',
41399             "url": 'get-invoice-data.php',
41400             "reader": {
41401                 "type": 'json'
41402                 "record": 'transaction',
41403                 "idProperty": 'id',
41404                 "totalRecords": 'total'
41405             })
41406         },
41407         "autoLoad": {
41408             "params": {
41409                 "startDate": '01/01/2008',
41410                 "endDate": '01/31/2008'
41411             }
41412         }
41413     },
41414     "headers": [
41415         {"header": "Customer", "width": 250, "dataIndex": 'customer', "sortable": true},
41416         {"header": "Invoice Number", "width": 120, "dataIndex": 'invNo', "sortable": true},
41417         {"header": "Invoice Date", "width": 100, "dataIndex": 'date', "renderer": Ext.util.Format.dateRenderer('M d, y'), "sortable": true},
41418         {"header": "Value", "width": 120, "dataIndex": 'value', "renderer": 'usMoney', "sortable": true}
41419     ]
41420 }
41421 </code></pre>
41422  * <p>When the above code fragment is passed through the <code>eval</code> function in the success handler
41423  * of the Ajax request, the result will be a config object which, when added to a Container, will cause instantiation
41424  * 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>
41425  * <p>Note: since the code above is <i>generated</i> by a server script, the <code>autoLoad</code> params for
41426  * the Store, the user's preferred date format, the metadata to allow generation of the Model layout, and the ColumnModel
41427  * can all be generated into the code since these are all known on the server.</p>
41428  *
41429  * @xtype container
41430  */
41431 Ext.define('Ext.container.Container', {
41432     extend: 'Ext.container.AbstractContainer',
41433     alias: 'widget.container',
41434     alternateClassName: 'Ext.Container',
41435
41436     /**
41437      * Return the immediate child Component in which the passed element is located.
41438      * @param el The element to test.
41439      * @return {Component} The child item which contains the passed element.
41440      */
41441     getChildByElement: function(el) {
41442         var item,
41443             itemEl,
41444             i = 0,
41445             it = this.items.items,
41446             ln = it.length;
41447
41448         el = Ext.getDom(el);
41449         for (; i < ln; i++) {
41450             item = it[i];
41451             itemEl = item.getEl();
41452             if ((itemEl.dom === el) || itemEl.contains(el)) {
41453                 return item;
41454             }
41455         }
41456         return null;
41457     }
41458 });
41459
41460 /**
41461  * @class Ext.toolbar.Fill
41462  * @extends Ext.Component
41463  * A non-rendering placeholder item which instructs the Toolbar's Layout to begin using
41464  * the right-justified button container.
41465  *
41466  * {@img Ext.toolbar.Fill/Ext.toolbar.Fill.png Toolbar Fill}
41467  * Example usage:
41468 <pre><code>
41469     Ext.create('Ext.panel.Panel', {
41470         title: 'Toolbar Fill Example',
41471         width: 300,
41472         height: 200,
41473         tbar : [
41474             'Item 1',
41475             {xtype: 'tbfill'}, // or '->'
41476             'Item 2'
41477         ],
41478         renderTo: Ext.getBody()
41479     });
41480 </code></pre>
41481  * @constructor
41482  * Creates a new Fill
41483  * @xtype tbfill
41484  */
41485 Ext.define('Ext.toolbar.Fill', {
41486     extend: 'Ext.Component',
41487     alias: 'widget.tbfill',
41488     alternateClassName: 'Ext.Toolbar.Fill',
41489     isFill : true,
41490     flex: 1
41491 });
41492 /**
41493  * @class Ext.toolbar.Item
41494  * @extends Ext.Component
41495  * The base class that other non-interacting Toolbar Item classes should extend in order to
41496  * get some basic common toolbar item functionality.
41497  * @constructor
41498  * Creates a new Item
41499  * @param {HTMLElement} el
41500  * @xtype tbitem
41501  */
41502 Ext.define('Ext.toolbar.Item', {
41503     extend: 'Ext.Component',
41504     alias: 'widget.tbitem',
41505     alternateClassName: 'Ext.Toolbar.Item',
41506     enable:Ext.emptyFn,
41507     disable:Ext.emptyFn,
41508     focus:Ext.emptyFn
41509     /**
41510      * @cfg {String} overflowText Text to be used for the menu if the item is overflowed.
41511      */
41512 });
41513 /**
41514  * @class Ext.toolbar.Separator
41515  * @extends Ext.toolbar.Item
41516  * A simple class that adds a vertical separator bar between toolbar items
41517  * (css class:<tt>'x-toolbar-separator'</tt>). 
41518  * {@img Ext.toolbar.Separator/Ext.toolbar.Separator.png Toolbar Separator}
41519  * Example usage:
41520  * <pre><code>
41521     Ext.create('Ext.panel.Panel', {
41522         title: 'Toolbar Seperator Example',
41523         width: 300,
41524         height: 200,
41525         tbar : [
41526             'Item 1',
41527             {xtype: 'tbseparator'}, // or '-'
41528             'Item 2'
41529         ],
41530         renderTo: Ext.getBody()
41531     }); 
41532 </code></pre>
41533  * @constructor
41534  * Creates a new Separator
41535  * @xtype tbseparator
41536  */
41537 Ext.define('Ext.toolbar.Separator', {
41538     extend: 'Ext.toolbar.Item',
41539     alias: 'widget.tbseparator',
41540     alternateClassName: 'Ext.Toolbar.Separator',
41541     baseCls: Ext.baseCSSPrefix + 'toolbar-separator',
41542     focusable: false
41543 });
41544 /**
41545  * @class Ext.menu.Manager
41546  * Provides a common registry of all menus on a page.
41547  * @singleton
41548  */
41549 Ext.define('Ext.menu.Manager', {
41550     singleton: true,
41551     requires: [
41552         'Ext.util.MixedCollection',
41553         'Ext.util.KeyMap'
41554     ],
41555     alternateClassName: 'Ext.menu.MenuMgr',
41556
41557     uses: ['Ext.menu.Menu'],
41558
41559     menus: {},
41560     groups: {},
41561     attached: false,
41562     lastShow: new Date(),
41563
41564     init: function() {
41565         var me = this;
41566         
41567         me.active = Ext.create('Ext.util.MixedCollection');
41568         Ext.getDoc().addKeyListener(27, function() {
41569             if (me.active.length > 0) {
41570                 me.hideAll();
41571             }
41572         }, me);
41573     },
41574
41575     /**
41576      * Hides all menus that are currently visible
41577      * @return {Boolean} success True if any active menus were hidden.
41578      */
41579     hideAll: function() {
41580         var active = this.active,
41581             c;
41582         if (active && active.length > 0) {
41583             c = active.clone();
41584             c.each(function(m) {
41585                 m.hide();
41586             });
41587             return true;
41588         }
41589         return false;
41590     },
41591
41592     onHide: function(m) {
41593         var me = this,
41594             active = me.active;
41595         active.remove(m);
41596         if (active.length < 1) {
41597             Ext.getDoc().un('mousedown', me.onMouseDown, me);
41598             me.attached = false;
41599         }
41600     },
41601
41602     onShow: function(m) {
41603         var me = this,
41604             active   = me.active,
41605             last     = active.last(),
41606             attached = me.attached,
41607             menuEl   = m.getEl(),
41608             zIndex;
41609
41610         me.lastShow = new Date();
41611         active.add(m);
41612         if (!attached) {
41613             Ext.getDoc().on('mousedown', me.onMouseDown, me);
41614             me.attached = true;
41615         }
41616         m.toFront();
41617     },
41618
41619     onBeforeHide: function(m) {
41620         if (m.activeChild) {
41621             m.activeChild.hide();
41622         }
41623         if (m.autoHideTimer) {
41624             clearTimeout(m.autoHideTimer);
41625             delete m.autoHideTimer;
41626         }
41627     },
41628
41629     onBeforeShow: function(m) {
41630         var active = this.active,
41631             parentMenu = m.parentMenu;
41632             
41633         active.remove(m);
41634         if (!parentMenu && !m.allowOtherMenus) {
41635             this.hideAll();
41636         }
41637         else if (parentMenu && parentMenu.activeChild && m != parentMenu.activeChild) {
41638             parentMenu.activeChild.hide();
41639         }
41640     },
41641
41642     // private
41643     onMouseDown: function(e) {
41644         var me = this,
41645             active = me.active,
41646             lastShow = me.lastShow;
41647
41648         if (Ext.Date.getElapsed(lastShow) > 50 && active.length > 0 && !e.getTarget('.' + Ext.baseCSSPrefix + 'menu')) {
41649             me.hideAll();
41650         }
41651     },
41652
41653     // private
41654     register: function(menu) {
41655         var me = this;
41656
41657         if (!me.active) {
41658             me.init();
41659         }
41660
41661         if (menu.floating) {
41662             me.menus[menu.id] = menu;
41663             menu.on({
41664                 beforehide: me.onBeforeHide,
41665                 hide: me.onHide,
41666                 beforeshow: me.onBeforeShow,
41667                 show: me.onShow,
41668                 scope: me
41669             });
41670         }
41671     },
41672
41673     /**
41674      * Returns a {@link Ext.menu.Menu} object
41675      * @param {String/Object} menu The string menu id, an existing menu object reference, or a Menu config that will
41676      * be used to generate and return a new Menu this.
41677      * @return {Ext.menu.Menu} The specified menu, or null if none are found
41678      */
41679     get: function(menu) {
41680         var menus = this.menus;
41681         
41682         if (typeof menu == 'string') { // menu id
41683             if (!menus) {  // not initialized, no menus to return
41684                 return null;
41685             }
41686             return menus[menu];
41687         } else if (menu.isMenu) {  // menu instance
41688             return menu;
41689         } else if (Ext.isArray(menu)) { // array of menu items
41690             return Ext.create('Ext.menu.Menu', {items:menu});
41691         } else { // otherwise, must be a config
41692             return Ext.ComponentManager.create(menu, 'menu');
41693         }
41694     },
41695
41696     // private
41697     unregister: function(menu) {
41698         var me = this,
41699             menus = me.menus,
41700             active = me.active;
41701
41702         delete menus[menu.id];
41703         active.remove(menu);
41704         menu.un({
41705             beforehide: me.onBeforeHide,
41706             hide: me.onHide,
41707             beforeshow: me.onBeforeShow,
41708             show: me.onShow,
41709             scope: me
41710         });
41711     },
41712
41713     // private
41714     registerCheckable: function(menuItem) {
41715         var groups  = this.groups,
41716             groupId = menuItem.group;
41717
41718         if (groupId) {
41719             if (!groups[groupId]) {
41720                 groups[groupId] = [];
41721             }
41722
41723             groups[groupId].push(menuItem);
41724         }
41725     },
41726
41727     // private
41728     unregisterCheckable: function(menuItem) {
41729         var groups  = this.groups,
41730             groupId = menuItem.group;
41731
41732         if (groupId) {
41733             Ext.Array.remove(groups[groupId], menuItem);
41734         }
41735     },
41736
41737     onCheckChange: function(menuItem, state) {
41738         var groups  = this.groups,
41739             groupId = menuItem.group,
41740             i       = 0,
41741             group, ln, curr;
41742
41743         if (groupId && state) {
41744             group = groups[groupId];
41745             ln = group.length;
41746             for (; i < ln; i++) {
41747                 curr = group[i];
41748                 if (curr != menuItem) {
41749                     curr.setChecked(false);
41750                 }
41751             }
41752         }
41753     }
41754 });
41755 /**
41756  * @class Ext.button.Button
41757  * @extends Ext.Component
41758
41759 Create simple buttons with this component. Customisations include {@link #config-iconAlign aligned}
41760 {@link #config-iconCls icons}, {@link #config-menu dropdown menus}, {@link #config-tooltip tooltips}
41761 and {@link #config-scale sizing options}. Specify a {@link #config-handler handler} to run code when
41762 a user clicks the button, or use {@link #config-listeners listeners} for other events such as
41763 {@link #events-mouseover mouseover}.
41764
41765 {@img Ext.button.Button/Ext.button.Button1.png Ext.button.Button component}
41766 Example usage:
41767
41768     Ext.create('Ext.Button', {
41769         text: 'Click me',
41770         renderTo: Ext.getBody(),        
41771         handler: function() {
41772             alert('You clicked the button!')
41773         }
41774     });
41775
41776 The {@link #handler} configuration can also be updated dynamically using the {@link #setHandler} method.
41777 Example usage:
41778
41779     Ext.create('Ext.Button', {
41780         text    : 'Dyanmic Handler Button',
41781         renderTo: Ext.getBody(),
41782         handler : function() {
41783             //this button will spit out a different number every time you click it.
41784             //so firstly we must check if that number is already set:
41785             if (this.clickCount) {
41786                 //looks like the property is already set, so lets just add 1 to that number and alert the user
41787                 this.clickCount++;
41788                 alert('You have clicked the button "' + this.clickCount + '" times.\n\nTry clicking it again..');
41789             } else {
41790                 //if the clickCount property is not set, we will set it and alert the user
41791                 this.clickCount = 1;
41792                 alert('You just clicked the button for the first time!\n\nTry pressing it again..');
41793             }
41794         }
41795     });
41796
41797 A button within a container:
41798
41799     Ext.create('Ext.Container', {
41800         renderTo: Ext.getBody(),
41801         items   : [
41802             {
41803                 xtype: 'button',
41804                 text : 'My Button'
41805             }
41806         ]
41807     });
41808
41809 A useful option of Button is the {@link #scale} configuration. This configuration has three different options:
41810 * `'small'`
41811 * `'medium'`
41812 * `'large'`
41813
41814 {@img Ext.button.Button/Ext.button.Button2.png Ext.button.Button component}
41815 Example usage:
41816
41817     Ext.create('Ext.Button', {
41818         renderTo: document.body,
41819         text    : 'Click me',
41820         scale   : 'large'
41821     });
41822
41823 Buttons can also be toggled. To enable this, you simple set the {@link #enableToggle} property to `true`.
41824 {@img Ext.button.Button/Ext.button.Button3.png Ext.button.Button component}
41825 Example usage:
41826
41827     Ext.create('Ext.Button', {
41828         renderTo: Ext.getBody(),
41829         text: 'Click Me',
41830         enableToggle: true
41831     });
41832
41833 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}
41834 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.
41835 You can change the alignment of the arrow using the {@link #arrowAlign} configuration on button.
41836 {@img Ext.button.Button/Ext.button.Button4.png Ext.button.Button component}
41837 Example usage:
41838
41839     Ext.create('Ext.Button', {
41840         text      : 'Menu button',
41841         renderTo  : Ext.getBody(),        
41842         arrowAlign: 'bottom',
41843         menu      : [
41844             {text: 'Item 1'},
41845             {text: 'Item 2'},
41846             {text: 'Item 3'},
41847             {text: 'Item 4'}
41848         ]
41849     });
41850
41851 Using listeners, you can easily listen to events fired by any component, using the {@link #listeners} configuration or using the {@link #addListener} method.
41852 Button has a variety of different listeners:
41853 * `click`
41854 * `toggle`
41855 * `mouseover`
41856 * `mouseout`
41857 * `mouseshow`
41858 * `menuhide`
41859 * `menutriggerover`
41860 * `menutriggerout`
41861
41862 Example usage:
41863
41864     Ext.create('Ext.Button', {
41865         text     : 'Button',
41866         renderTo : Ext.getBody(),
41867         listeners: {
41868             click: function() {
41869                 //this == the button, as we are in the local scope
41870                 this.setText('I was clicked!');
41871             },
41872             mouseover: function() {
41873                 //set a new config which says we moused over, if not already set
41874                 if (!this.mousedOver) {
41875                     this.mousedOver = true;
41876                     alert('You moused over a button!\n\nI wont do this again.');
41877                 }
41878             }
41879         }
41880     });
41881
41882  * @constructor
41883  * Create a new button
41884  * @param {Object} config The config object
41885  * @xtype button
41886  * @markdown
41887  * @docauthor Robert Dougan <rob@sencha.com>
41888  */
41889 Ext.define('Ext.button.Button', {
41890
41891     /* Begin Definitions */
41892     alias: 'widget.button',
41893     extend: 'Ext.Component',
41894
41895     requires: [
41896         'Ext.menu.Manager',
41897         'Ext.util.ClickRepeater',
41898         'Ext.layout.component.Button',
41899         'Ext.util.TextMetrics',
41900         'Ext.util.KeyMap'
41901     ],
41902
41903     alternateClassName: 'Ext.Button',
41904     /* End Definitions */
41905
41906     isButton: true,
41907     componentLayout: 'button',
41908
41909     /**
41910      * Read-only. True if this button is hidden
41911      * @type Boolean
41912      */
41913     hidden: false,
41914
41915     /**
41916      * Read-only. True if this button is disabled
41917      * @type Boolean
41918      */
41919     disabled: false,
41920
41921     /**
41922      * Read-only. True if this button is pressed (only if enableToggle = true)
41923      * @type Boolean
41924      */
41925     pressed: false,
41926
41927     /**
41928      * @cfg {String} text The button text to be used as innerHTML (html tags are accepted)
41929      */
41930
41931     /**
41932      * @cfg {String} icon The path to an image to display in the button (the image will be set as the background-image
41933      * CSS property of the button by default, so if you want a mixed icon/text button, set cls:'x-btn-text-icon')
41934      */
41935
41936     /**
41937      * @cfg {Function} handler A function called when the button is clicked (can be used instead of click event).
41938      * The handler is passed the following parameters:<div class="mdetail-params"><ul>
41939      * <li><code>b</code> : Button<div class="sub-desc">This Button.</div></li>
41940      * <li><code>e</code> : EventObject<div class="sub-desc">The click event.</div></li>
41941      * </ul></div>
41942      */
41943
41944     /**
41945      * @cfg {Number} minWidth The minimum width for this button (used to give a set of buttons a common width).
41946      * See also {@link Ext.panel.Panel}.<tt>{@link Ext.panel.Panel#minButtonWidth minButtonWidth}</tt>.
41947      */
41948
41949     /**
41950      * @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
41951      */
41952
41953     /**
41954      * @cfg {Boolean} hidden True to start hidden (defaults to false)
41955      */
41956
41957     /**
41958      * @cfg {Boolean} disabled True to start disabled (defaults to false)
41959      */
41960
41961     /**
41962      * @cfg {Boolean} pressed True to start pressed (only if enableToggle = true)
41963      */
41964
41965     /**
41966      * @cfg {String} toggleGroup The group this toggle button is a member of (only 1 per group can be pressed)
41967      */
41968
41969     /**
41970      * @cfg {Boolean/Object} repeat True to repeat fire the click event while the mouse is down. This can also be
41971      * a {@link Ext.util.ClickRepeater ClickRepeater} config object (defaults to false).
41972      */
41973
41974     /**
41975      * @cfg {Number} tabIndex Set a DOM tabIndex for this button (defaults to undefined)
41976      */
41977
41978     /**
41979      * @cfg {Boolean} allowDepress
41980      * False to not allow a pressed Button to be depressed (defaults to undefined). Only valid when {@link #enableToggle} is true.
41981      */
41982
41983     /**
41984      * @cfg {Boolean} enableToggle
41985      * True to enable pressed/not pressed toggling (defaults to false)
41986      */
41987     enableToggle: false,
41988
41989     /**
41990      * @cfg {Function} toggleHandler
41991      * Function called when a Button with {@link #enableToggle} set to true is clicked. Two arguments are passed:<ul class="mdetail-params">
41992      * <li><b>button</b> : Ext.button.Button<div class="sub-desc">this Button object</div></li>
41993      * <li><b>state</b> : Boolean<div class="sub-desc">The next state of the Button, true means pressed.</div></li>
41994      * </ul>
41995      */
41996
41997     /**
41998      * @cfg {Mixed} menu
41999      * Standard menu attribute consisting of a reference to a menu object, a menu id or a menu config blob (defaults to undefined).
42000      */
42001
42002     /**
42003      * @cfg {String} menuAlign
42004      * The position to align the menu to (see {@link Ext.core.Element#alignTo} for more details, defaults to 'tl-bl?').
42005      */
42006     menuAlign: 'tl-bl?',
42007
42008     /**
42009      * @cfg {String} overflowText If used in a {@link Ext.toolbar.Toolbar Toolbar}, the
42010      * text to be used if this item is shown in the overflow menu. See also
42011      * {@link Ext.toolbar.Item}.<code>{@link Ext.toolbar.Item#overflowText overflowText}</code>.
42012      */
42013
42014     /**
42015      * @cfg {String} iconCls
42016      * A css class which sets a background image to be used as the icon for this button
42017      */
42018
42019     /**
42020      * @cfg {String} type
42021      * submit, reset or button - defaults to 'button'
42022      */
42023     type: 'button',
42024
42025     /**
42026      * @cfg {String} clickEvent
42027      * The DOM event that will fire the handler of the button. This can be any valid event name (dblclick, contextmenu).
42028      * Defaults to <tt>'click'</tt>.
42029      */
42030     clickEvent: 'click',
42031     
42032     /**
42033      * @cfg {Boolean} preventDefault
42034      * True to prevent the default action when the {@link #clickEvent} is processed. Defaults to true.
42035      */
42036     preventDefault: true,
42037
42038     /**
42039      * @cfg {Boolean} handleMouseEvents
42040      * False to disable visual cues on mouseover, mouseout and mousedown (defaults to true)
42041      */
42042     handleMouseEvents: true,
42043
42044     /**
42045      * @cfg {String} tooltipType
42046      * The type of tooltip to use. Either 'qtip' (default) for QuickTips or 'title' for title attribute.
42047      */
42048     tooltipType: 'qtip',
42049
42050     /**
42051      * @cfg {String} baseCls
42052      * The base CSS class to add to all buttons. (Defaults to 'x-btn')
42053      */
42054     baseCls: Ext.baseCSSPrefix + 'btn',
42055
42056     /**
42057      * @cfg {String} pressedCls
42058      * The CSS class to add to a button when it is in the pressed state. (Defaults to 'x-btn-pressed')
42059      */
42060     pressedCls: 'pressed',
42061     
42062     /**
42063      * @cfg {String} overCls
42064      * The CSS class to add to a button when it is in the over (hovered) state. (Defaults to 'x-btn-over')
42065      */
42066     overCls: 'over',
42067     
42068     /**
42069      * @cfg {String} focusCls
42070      * The CSS class to add to a button when it is in the focussed state. (Defaults to 'x-btn-focus')
42071      */
42072     focusCls: 'focus',
42073     
42074     /**
42075      * @cfg {String} menuActiveCls
42076      * The CSS class to add to a button when it's menu is active. (Defaults to 'x-btn-menu-active')
42077      */
42078     menuActiveCls: 'menu-active',
42079
42080     ariaRole: 'button',
42081
42082     // inherited
42083     renderTpl:
42084         '<em class="{splitCls}">' +
42085             '<tpl if="href">' +
42086                 '<a href="{href}" target="{target}"<tpl if="tabIndex"> tabIndex="{tabIndex}"</tpl> role="link">' +
42087                     '<span class="{baseCls}-inner">{text}</span>' +
42088                 '</a>' +
42089             '</tpl>' +
42090             '<tpl if="!href">' +
42091                 '<button type="{type}" hidefocus="true"' +
42092                     // the autocomplete="off" is required to prevent Firefox from remembering
42093                     // the button's disabled state between page reloads.
42094                     '<tpl if="tabIndex"> tabIndex="{tabIndex}"</tpl> role="button" autocomplete="off">' +
42095                     '<span class="{baseCls}-inner" style="{innerSpanStyle}">{text}</span>' +
42096                 '</button>' +
42097             '</tpl>' +
42098         '</em>' ,
42099
42100     /**
42101      * @cfg {String} scale
42102      * <p>(Optional) The size of the Button. Three values are allowed:</p>
42103      * <ul class="mdetail-params">
42104      * <li>'small'<div class="sub-desc">Results in the button element being 16px high.</div></li>
42105      * <li>'medium'<div class="sub-desc">Results in the button element being 24px high.</div></li>
42106      * <li>'large'<div class="sub-desc">Results in the button element being 32px high.</div></li>
42107      * </ul>
42108      * <p>Defaults to <b><tt>'small'</tt></b>.</p>
42109      */
42110     scale: 'small',
42111     
42112     /**
42113      * @private An array of allowed scales.
42114      */
42115     allowedScales: ['small', 'medium', 'large'],
42116     
42117     /**
42118      * @cfg {Object} scope The scope (<tt><b>this</b></tt> reference) in which the
42119      * <code>{@link #handler}</code> and <code>{@link #toggleHandler}</code> is
42120      * executed. Defaults to this Button.
42121      */
42122
42123     /**
42124      * @cfg {String} iconAlign
42125      * <p>(Optional) The side of the Button box to render the icon. Four values are allowed:</p>
42126      * <ul class="mdetail-params">
42127      * <li>'top'<div class="sub-desc"></div></li>
42128      * <li>'right'<div class="sub-desc"></div></li>
42129      * <li>'bottom'<div class="sub-desc"></div></li>
42130      * <li>'left'<div class="sub-desc"></div></li>
42131      * </ul>
42132      * <p>Defaults to <b><tt>'left'</tt></b>.</p>
42133      */
42134     iconAlign: 'left',
42135
42136     /**
42137      * @cfg {String} arrowAlign
42138      * <p>(Optional) The side of the Button box to render the arrow if the button has an associated {@link #menu}.
42139      * Two values are allowed:</p>
42140      * <ul class="mdetail-params">
42141      * <li>'right'<div class="sub-desc"></div></li>
42142      * <li>'bottom'<div class="sub-desc"></div></li>
42143      * </ul>
42144      * <p>Defaults to <b><tt>'right'</tt></b>.</p>
42145      */
42146     arrowAlign: 'right',
42147
42148     /**
42149      * @cfg {String} arrowCls
42150      * <p>(Optional) The className used for the inner arrow element if the button has a menu.</p>
42151      */
42152     arrowCls: 'arrow',
42153
42154     /**
42155      * @cfg {Ext.Template} template (Optional)
42156      * <p>A {@link Ext.Template Template} used to create the Button's DOM structure.</p>
42157      * Instances, or subclasses which need a different DOM structure may provide a different
42158      * template layout in conjunction with an implementation of {@link #getTemplateArgs}.
42159      * @type Ext.Template
42160      * @property template
42161      */
42162
42163     /**
42164      * @cfg {String} cls
42165      * A CSS class string to apply to the button's main element.
42166      */
42167
42168     /**
42169      * @property menu
42170      * @type Menu
42171      * The {@link Ext.menu.Menu Menu} object associated with this Button when configured with the {@link #menu} config option.
42172      */
42173
42174     /**
42175      * @cfg {Boolean} autoWidth
42176      * By default, if a width is not specified the button will attempt to stretch horizontally to fit its content.
42177      * If the button is being managed by a width sizing layout (hbox, fit, anchor), set this to false to prevent
42178      * the button from doing this automatic sizing.
42179      * Defaults to <tt>undefined</tt>.
42180      */
42181      
42182     maskOnDisable: false,
42183
42184     // inherit docs
42185     initComponent: function() {
42186         var me = this;
42187         me.callParent(arguments);
42188
42189         me.addEvents(
42190             /**
42191              * @event click
42192              * Fires when this button is clicked
42193              * @param {Button} this
42194              * @param {EventObject} e The click event
42195              */
42196             'click',
42197
42198             /**
42199              * @event toggle
42200              * Fires when the 'pressed' state of this button changes (only if enableToggle = true)
42201              * @param {Button} this
42202              * @param {Boolean} pressed
42203              */
42204             'toggle',
42205
42206             /**
42207              * @event mouseover
42208              * Fires when the mouse hovers over the button
42209              * @param {Button} this
42210              * @param {Event} e The event object
42211              */
42212             'mouseover',
42213
42214             /**
42215              * @event mouseout
42216              * Fires when the mouse exits the button
42217              * @param {Button} this
42218              * @param {Event} e The event object
42219              */
42220             'mouseout',
42221
42222             /**
42223              * @event menushow
42224              * If this button has a menu, this event fires when it is shown
42225              * @param {Button} this
42226              * @param {Menu} menu
42227              */
42228             'menushow',
42229
42230             /**
42231              * @event menuhide
42232              * If this button has a menu, this event fires when it is hidden
42233              * @param {Button} this
42234              * @param {Menu} menu
42235              */
42236             'menuhide',
42237
42238             /**
42239              * @event menutriggerover
42240              * If this button has a menu, this event fires when the mouse enters the menu triggering element
42241              * @param {Button} this
42242              * @param {Menu} menu
42243              * @param {EventObject} e
42244              */
42245             'menutriggerover',
42246
42247             /**
42248              * @event menutriggerout
42249              * If this button has a menu, this event fires when the mouse leaves the menu triggering element
42250              * @param {Button} this
42251              * @param {Menu} menu
42252              * @param {EventObject} e
42253              */
42254             'menutriggerout'
42255         );
42256
42257         if (me.menu) {
42258             // Flag that we'll have a splitCls
42259             me.split = true;
42260
42261             // retrieve menu by id or instantiate instance if needed
42262             me.menu = Ext.menu.Manager.get(me.menu);
42263             me.menu.ownerCt = me;
42264         }
42265
42266         // Accept url as a synonym for href
42267         if (me.url) {
42268             me.href = me.url;
42269         }
42270
42271         // preventDefault defaults to false for links
42272         if (me.href && !me.hasOwnProperty('preventDefault')) {
42273             me.preventDefault = false;
42274         }
42275
42276         if (Ext.isString(me.toggleGroup)) {
42277             me.enableToggle = true;
42278         }
42279
42280     },
42281
42282     // private
42283     initAria: function() {
42284         this.callParent();
42285         var actionEl = this.getActionEl();
42286         if (this.menu) {
42287             actionEl.dom.setAttribute('aria-haspopup', true);
42288         }
42289     },
42290
42291     // inherit docs
42292     getActionEl: function() {
42293         return this.btnEl;
42294     },
42295
42296     // inherit docs
42297     getFocusEl: function() {
42298         return this.btnEl;
42299     },
42300
42301     // private
42302     setButtonCls: function() {
42303         var me = this,
42304             el = me.el,
42305             cls = [];
42306
42307         if (me.useSetClass) {
42308             if (!Ext.isEmpty(me.oldCls)) {
42309                 me.removeClsWithUI(me.oldCls);
42310                 me.removeClsWithUI(me.pressedCls);
42311             }
42312             
42313             // Check whether the button has an icon or not, and if it has an icon, what is th alignment
42314             if (me.iconCls || me.icon) {
42315                 if (me.text) {
42316                     cls.push('icon-text-' + me.iconAlign);
42317                 } else {
42318                     cls.push('icon');
42319                 }
42320             } else if (me.text) {
42321                 cls.push('noicon');
42322             }
42323             
42324             me.oldCls = cls;
42325             me.addClsWithUI(cls);
42326             me.addClsWithUI(me.pressed ? me.pressedCls : null);
42327         }
42328     },
42329     
42330     // private
42331     onRender: function(ct, position) {
42332         // classNames for the button
42333         var me = this,
42334             repeater, btn;
42335             
42336         // Apply the renderData to the template args
42337         Ext.applyIf(me.renderData, me.getTemplateArgs());
42338
42339         // Extract the button and the button wrapping element
42340         Ext.applyIf(me.renderSelectors, {
42341             btnEl  : me.href ? 'a' : 'button',
42342             btnWrap: 'em',
42343             btnInnerEl: '.' + me.baseCls + '-inner'
42344         });
42345         
42346         if (me.scale) {
42347             me.ui = me.ui + '-' + me.scale;
42348         }
42349
42350         // Render internal structure
42351         me.callParent(arguments);
42352
42353         // If it is a split button + has a toolip for the arrow
42354         if (me.split && me.arrowTooltip) {
42355             me.arrowEl.dom[me.tooltipType] = me.arrowTooltip;
42356         }
42357
42358         // Add listeners to the focus and blur events on the element
42359         me.mon(me.btnEl, {
42360             scope: me,
42361             focus: me.onFocus,
42362             blur : me.onBlur
42363         });
42364
42365         // Set btn as a local variable for easy access
42366         btn = me.el;
42367
42368         if (me.icon) {
42369             me.setIcon(me.icon);
42370         }
42371
42372         if (me.iconCls) {
42373             me.setIconCls(me.iconCls);
42374         }
42375
42376         if (me.tooltip) {
42377             me.setTooltip(me.tooltip, true);
42378         }
42379
42380         // Add the mouse events to the button
42381         if (me.handleMouseEvents) {
42382             me.mon(btn, {
42383                 scope: me,
42384                 mouseover: me.onMouseOver,
42385                 mouseout: me.onMouseOut,
42386                 mousedown: me.onMouseDown
42387             });
42388
42389             if (me.split) {
42390                 me.mon(btn, {
42391                     mousemove: me.onMouseMove,
42392                     scope: me
42393                 });
42394             }
42395         }
42396
42397         // Check if the button has a menu
42398         if (me.menu) {
42399             me.mon(me.menu, {
42400                 scope: me,
42401                 show: me.onMenuShow,
42402                 hide: me.onMenuHide
42403             });
42404
42405             me.keyMap = Ext.create('Ext.util.KeyMap', me.el, {
42406                 key: Ext.EventObject.DOWN,
42407                 handler: me.onDownKey,
42408                 scope: me
42409             });
42410         }
42411
42412         // Check if it is a repeat button
42413         if (me.repeat) {
42414             repeater = Ext.create('Ext.util.ClickRepeater', btn, Ext.isObject(me.repeat) ? me.repeat: {});
42415             me.mon(repeater, 'click', me.onRepeatClick, me);
42416         } else {
42417             me.mon(btn, me.clickEvent, me.onClick, me);
42418         }
42419
42420         // Register the button in the toggle manager
42421         Ext.ButtonToggleManager.register(me);
42422     },
42423
42424     /**
42425      * <p>This method returns an object which provides substitution parameters for the {@link #renderTpl XTemplate} used
42426      * to create this Button's DOM structure.</p>
42427      * <p>Instances or subclasses which use a different Template to create a different DOM structure may need to provide their
42428      * own implementation of this method.</p>
42429      * <p>The default implementation which provides data for the default {@link #template} returns an Object containing the
42430      * following properties:</p><div class="mdetail-params"><ul>
42431      * <li><code>type</code> : The &lt;button&gt;'s {@link #type}</li>
42432      * <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>
42433      * <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>
42434      * <li><code>text</code> : The {@link #text} to display ion the Button.</li>
42435      * <li><code>tabIndex</code> : The tab index within the input flow.</li>
42436      * </ul></div>
42437      * @return {Array} Substitution data for a Template.
42438     */
42439     getTemplateArgs: function() {
42440         var me = this,
42441             persistentPadding = me.getPersistentBtnPadding(),
42442             innerSpanStyle = '';
42443
42444         // Create negative margin offsets to counteract persistent button padding if needed
42445         if (Math.max.apply(Math, persistentPadding) > 0) {
42446             innerSpanStyle = 'margin:' + Ext.Array.map(persistentPadding, function(pad) {
42447                 return -pad + 'px';
42448             }).join(' ');
42449         }
42450
42451         return {
42452             href     : me.getHref(),
42453             target   : me.target || '_blank',
42454             type     : me.type,
42455             splitCls : me.getSplitCls(),
42456             cls      : me.cls,
42457             text     : me.text || '&#160;',
42458             tabIndex : me.tabIndex,
42459             innerSpanStyle: innerSpanStyle
42460         };
42461     },
42462
42463     /**
42464      * @private
42465      * If there is a configured href for this Button, returns the href with parameters appended.
42466      * @returns The href string with parameters appended.
42467      */
42468     getHref: function() {
42469         var me = this;
42470         return me.href ? Ext.urlAppend(me.href, me.params + Ext.Object.toQueryString(Ext.apply(Ext.apply({}, me.baseParams)))) : false;
42471     },
42472
42473     /**
42474      * <p><b>Only valid if the Button was originally configured with a {@link #url}</b></p>
42475      * <p>Sets the href of the link dynamically according to the params passed, and any {@link #baseParams} configured.</p>
42476      * @param {Object} Parameters to use in the href URL.
42477      */
42478     setParams: function(p) {
42479         this.params = p;
42480         this.btnEl.dom.href = this.getHref();
42481     },
42482
42483     getSplitCls: function() {
42484         var me = this;
42485         return me.split ? (me.baseCls + '-' + me.arrowCls) + ' ' + (me.baseCls + '-' + me.arrowCls + '-' + me.arrowAlign) : '';
42486     },
42487
42488     // private
42489     afterRender: function() {
42490         var me = this;
42491         me.useSetClass = true;
42492         me.setButtonCls();
42493         me.doc = Ext.getDoc();
42494         this.callParent(arguments);
42495     },
42496
42497     /**
42498      * Sets the CSS class that provides a background image to use as the button's icon.  This method also changes
42499      * the value of the {@link #iconCls} config internally.
42500      * @param {String} cls The CSS class providing the icon image
42501      * @return {Ext.button.Button} this
42502      */
42503     setIconCls: function(cls) {
42504         var me = this,
42505             btnInnerEl = me.btnInnerEl;
42506         if (btnInnerEl) {
42507             // Remove the previous iconCls from the button
42508             btnInnerEl.removeCls(me.iconCls);
42509             btnInnerEl.addCls(cls || '');
42510             me.setButtonCls();
42511         }
42512         me.iconCls = cls;
42513         return me;
42514     },
42515
42516     /**
42517      * Sets the tooltip for this Button.
42518      * @param {String/Object} tooltip. This may be:<div class="mdesc-details"><ul>
42519      * <li><b>String</b> : A string to be used as innerHTML (html tags are accepted) to show in a tooltip</li>
42520      * <li><b>Object</b> : A configuration object for {@link Ext.tip.QuickTipManager#register}.</li>
42521      * </ul></div>
42522      * @return {Ext.button.Button} this
42523      */
42524     setTooltip: function(tooltip, initial) {
42525         var me = this;
42526
42527         if (me.rendered) {
42528             if (!initial) {
42529                 me.clearTip();
42530             }
42531             if (Ext.isObject(tooltip)) {
42532                 Ext.tip.QuickTipManager.register(Ext.apply({
42533                     target: me.btnEl.id
42534                 },
42535                 tooltip));
42536                 me.tooltip = tooltip;
42537             } else {
42538                 me.btnEl.dom.setAttribute('data-' + this.tooltipType, tooltip);
42539             }
42540         } else {
42541             me.tooltip = tooltip;
42542         }
42543         return me;
42544     },
42545
42546     // private
42547     getRefItems: function(deep){
42548         var menu = this.menu,
42549             items;
42550
42551         if (menu) {
42552             items = menu.getRefItems(deep);
42553             items.unshift(menu);
42554         }
42555         return items || [];
42556     },
42557
42558     // private
42559     clearTip: function() {
42560         if (Ext.isObject(this.tooltip)) {
42561             Ext.tip.QuickTipManager.unregister(this.btnEl);
42562         }
42563     },
42564
42565     // private
42566     beforeDestroy: function() {
42567         var me = this;
42568         if (me.rendered) {
42569             me.clearTip();
42570         }
42571         if (me.menu && me.destroyMenu !== false) {
42572             Ext.destroy(me.btnEl, me.btnInnerEl, me.menu);
42573         }
42574         Ext.destroy(me.repeater);
42575     },
42576
42577     // private
42578     onDestroy: function() {
42579         var me = this;
42580         if (me.rendered) {
42581             me.doc.un('mouseover', me.monitorMouseOver, me);
42582             me.doc.un('mouseup', me.onMouseUp, me);
42583             delete me.doc;
42584             delete me.btnEl;
42585             delete me.btnInnerEl;
42586             Ext.ButtonToggleManager.unregister(me);
42587             
42588             Ext.destroy(me.keyMap);
42589             delete me.keyMap;
42590         }
42591         me.callParent();
42592     },
42593
42594     /**
42595      * Assigns this Button's click handler
42596      * @param {Function} handler The function to call when the button is clicked
42597      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the handler function is executed.
42598      * Defaults to this Button.
42599      * @return {Ext.button.Button} this
42600      */
42601     setHandler: function(handler, scope) {
42602         this.handler = handler;
42603         this.scope = scope;
42604         return this;
42605     },
42606
42607     /**
42608      * Sets this Button's text
42609      * @param {String} text The button text
42610      * @return {Ext.button.Button} this
42611      */
42612     setText: function(text) {
42613         var me = this;
42614         me.text = text;
42615         if (me.el) {
42616             me.btnInnerEl.update(text || '&#160;');
42617             me.setButtonCls();
42618         }
42619         me.doComponentLayout();
42620         return me;
42621     },
42622
42623     /**
42624      * Sets the background image (inline style) of the button.  This method also changes
42625      * the value of the {@link #icon} config internally.
42626      * @param {String} icon The path to an image to display in the button
42627      * @return {Ext.button.Button} this
42628      */
42629     setIcon: function(icon) {
42630         var me = this,
42631             btnInnerEl = me.btnInnerEl;
42632         me.icon = icon;
42633         if (btnInnerEl) {
42634             btnInnerEl.setStyle('background-image', icon ? 'url(' + icon + ')': '');
42635             me.setButtonCls();
42636         }
42637         return me;
42638     },
42639
42640     /**
42641      * Gets the text for this Button
42642      * @return {String} The button text
42643      */
42644     getText: function() {
42645         return this.text;
42646     },
42647
42648     /**
42649      * If a state it passed, it becomes the pressed state otherwise the current state is toggled.
42650      * @param {Boolean} state (optional) Force a particular state
42651      * @param {Boolean} supressEvent (optional) True to stop events being fired when calling this method.
42652      * @return {Ext.button.Button} this
42653      */
42654     toggle: function(state, suppressEvent) {
42655         var me = this;
42656         state = state === undefined ? !me.pressed: !!state;
42657         if (state !== me.pressed) {
42658             if (me.rendered) {
42659                 me[state ? 'addClsWithUI': 'removeClsWithUI'](me.pressedCls);
42660             }
42661             me.btnEl.dom.setAttribute('aria-pressed', state);
42662             me.pressed = state;
42663             if (!suppressEvent) {
42664                 me.fireEvent('toggle', me, state);
42665                 Ext.callback(me.toggleHandler, me.scope || me, [me, state]);
42666             }
42667         }
42668         return me;
42669     },
42670
42671     /**
42672      * Show this button's menu (if it has one)
42673      */
42674     showMenu: function() {
42675         var me = this;
42676         if (me.rendered && me.menu) {
42677             if (me.tooltip) {
42678                 Ext.tip.QuickTipManager.getQuickTip().cancelShow(me.btnEl);
42679             }
42680             if (me.menu.isVisible()) {
42681                 me.menu.hide();
42682             }
42683
42684             me.menu.showBy(me.el, me.menuAlign);
42685         }
42686         return me;
42687     },
42688
42689     /**
42690      * Hide this button's menu (if it has one)
42691      */
42692     hideMenu: function() {
42693         if (this.hasVisibleMenu()) {
42694             this.menu.hide();
42695         }
42696         return this;
42697     },
42698
42699     /**
42700      * Returns true if the button has a menu and it is visible
42701      * @return {Boolean}
42702      */
42703     hasVisibleMenu: function() {
42704         var menu = this.menu;
42705         return menu && menu.rendered && menu.isVisible();
42706     },
42707
42708     // private
42709     onRepeatClick: function(repeat, e) {
42710         this.onClick(e);
42711     },
42712
42713     // private
42714     onClick: function(e) {
42715         var me = this;
42716         if (me.preventDefault || (me.disabled && me.getHref()) && e) {
42717             e.preventDefault();
42718         }
42719         if (e.button !== 0) {
42720             return;
42721         }
42722         if (!me.disabled) {
42723             if (me.enableToggle && (me.allowDepress !== false || !me.pressed)) {
42724                 me.toggle();
42725             }
42726             if (me.menu && !me.hasVisibleMenu() && !me.ignoreNextClick) {
42727                 me.showMenu();
42728             }
42729             me.fireEvent('click', me, e);
42730             if (me.handler) {
42731                 me.handler.call(me.scope || me, me, e);
42732             }
42733             me.onBlur();
42734         }
42735     },
42736
42737     /**
42738      * @private mouseover handler called when a mouseover event occurs anywhere within the encapsulating element.
42739      * The targets are interrogated to see what is being entered from where.
42740      * @param e
42741      */
42742     onMouseOver: function(e) {
42743         var me = this;
42744         if (!me.disabled && !e.within(me.el, true, true)) {
42745             me.onMouseEnter(e);
42746         }
42747     },
42748
42749     /**
42750      * @private mouseout handler called when a mouseout event occurs anywhere within the encapsulating element -
42751      * or the mouse leaves the encapsulating element.
42752      * The targets are interrogated to see what is being exited to where.
42753      * @param e
42754      */
42755     onMouseOut: function(e) {
42756         var me = this;
42757         if (!e.within(me.el, true, true)) {
42758             if (me.overMenuTrigger) {
42759                 me.onMenuTriggerOut(e);
42760             }
42761             me.onMouseLeave(e);
42762         }
42763     },
42764
42765     /**
42766      * @private mousemove handler called when the mouse moves anywhere within the encapsulating element.
42767      * The position is checked to determine if the mouse is entering or leaving the trigger area. Using
42768      * mousemove to check this is more resource intensive than we'd like, but it is necessary because
42769      * the trigger area does not line up exactly with sub-elements so we don't always get mouseover/out
42770      * events when needed. In the future we should consider making the trigger a separate element that
42771      * is absolutely positioned and sized over the trigger area.
42772      */
42773     onMouseMove: function(e) {
42774         var me = this,
42775             el = me.el,
42776             over = me.overMenuTrigger,
42777             overlap, btnSize;
42778
42779         if (me.split) {
42780             if (me.arrowAlign === 'right') {
42781                 overlap = e.getX() - el.getX();
42782                 btnSize = el.getWidth();
42783             } else {
42784                 overlap = e.getY() - el.getY();
42785                 btnSize = el.getHeight();
42786             }
42787
42788             if (overlap > (btnSize - me.getTriggerSize())) {
42789                 if (!over) {
42790                     me.onMenuTriggerOver(e);
42791                 }
42792             } else {
42793                 if (over) {
42794                     me.onMenuTriggerOut(e);
42795                 }
42796             }
42797         }
42798     },
42799
42800     /**
42801      * @private Measures the size of the trigger area for menu and split buttons. Will be a width for
42802      * a right-aligned trigger and a height for a bottom-aligned trigger. Cached after first measurement.
42803      */
42804     getTriggerSize: function() {
42805         var me = this,
42806             size = me.triggerSize,
42807             side, sideFirstLetter, undef;
42808             
42809         if (size === undef) {
42810             side = me.arrowAlign;
42811             sideFirstLetter = side.charAt(0);
42812             size = me.triggerSize = me.el.getFrameWidth(sideFirstLetter) + me.btnWrap.getFrameWidth(sideFirstLetter) + (me.frameSize && me.frameSize[side] || 0);
42813         }
42814         return size;
42815     },
42816
42817     /**
42818      * @private virtual mouseenter handler called when it is detected that the mouseout event
42819      * signified the mouse entering the encapsulating element.
42820      * @param e
42821      */
42822     onMouseEnter: function(e) {
42823         var me = this;
42824         me.addClsWithUI(me.overCls);
42825         me.fireEvent('mouseover', me, e);
42826     },
42827
42828     /**
42829      * @private virtual mouseleave handler called when it is detected that the mouseover event
42830      * signified the mouse entering the encapsulating element.
42831      * @param e
42832      */
42833     onMouseLeave: function(e) {
42834         var me = this;
42835         me.removeClsWithUI(me.overCls);
42836         me.fireEvent('mouseout', me, e);
42837     },
42838
42839     /**
42840      * @private virtual mouseenter handler called when it is detected that the mouseover event
42841      * signified the mouse entering the arrow area of the button - the <em>.
42842      * @param e
42843      */
42844     onMenuTriggerOver: function(e) {
42845         var me = this;
42846         me.overMenuTrigger = true;
42847         me.fireEvent('menutriggerover', me, me.menu, e);
42848     },
42849
42850     /**
42851      * @private virtual mouseleave handler called when it is detected that the mouseout event
42852      * signified the mouse leaving the arrow area of the button - the <em>.
42853      * @param e
42854      */
42855     onMenuTriggerOut: function(e) {
42856         var me = this;
42857         delete me.overMenuTrigger;
42858         me.fireEvent('menutriggerout', me, me.menu, e);
42859     },
42860     
42861     // inherit docs
42862     enable : function(silent) {
42863         var me = this;
42864
42865         me.callParent(arguments);
42866         
42867         me.removeClsWithUI('disabled');
42868
42869         return me;
42870     },
42871
42872     // inherit docs
42873     disable : function(silent) {
42874         var me = this;
42875         
42876         me.callParent(arguments);
42877         
42878         me.addClsWithUI('disabled');
42879
42880         return me;
42881     },
42882     
42883     /**
42884      * Method to change the scale of the button. See {@link #scale} for allowed configurations.
42885      * @param {String} scale The scale to change to.
42886      */
42887     setScale: function(scale) {
42888         var me = this,
42889             ui = me.ui.replace('-' + me.scale, '');
42890         
42891         //check if it is an allowed scale
42892         if (!Ext.Array.contains(me.allowedScales, scale)) {
42893             throw('#setScale: scale must be an allowed scale (' + me.allowedScales.join(', ') + ')');
42894         }
42895         
42896         me.scale = scale;
42897         me.setUI(ui);
42898     },
42899     
42900     // inherit docs
42901     setUI: function(ui) {
42902         var me = this;
42903         
42904         //we need to append the scale to the UI, if not already done
42905         if (me.scale && !ui.match(me.scale)) {
42906             ui = ui + '-' + me.scale;
42907         }
42908         
42909         me.callParent([ui]);
42910         
42911         // Set all the state classNames, as they need to include the UI
42912         // me.disabledCls += ' ' + me.baseCls + '-' + me.ui + '-disabled';
42913     },
42914     
42915     // private
42916     onFocus: function(e) {
42917         var me = this;
42918         if (!me.disabled) {
42919             me.addClsWithUI(me.focusCls);
42920         }
42921     },
42922
42923     // private
42924     onBlur: function(e) {
42925         var me = this;
42926         me.removeClsWithUI(me.focusCls);
42927     },
42928
42929     // private
42930     onMouseDown: function(e) {
42931         var me = this;
42932         if (!me.disabled && e.button === 0) {
42933             me.addClsWithUI(me.pressedCls);
42934             me.doc.on('mouseup', me.onMouseUp, me);
42935         }
42936     },
42937     // private
42938     onMouseUp: function(e) {
42939         var me = this;
42940         if (e.button === 0) {
42941             if (!me.pressed) {
42942                 me.removeClsWithUI(me.pressedCls);
42943             }
42944             me.doc.un('mouseup', me.onMouseUp, me);
42945         }
42946     },
42947     // private
42948     onMenuShow: function(e) {
42949         var me = this;
42950         me.ignoreNextClick = 0;
42951         me.addClsWithUI(me.menuActiveCls);
42952         me.fireEvent('menushow', me, me.menu);
42953     },
42954
42955     // private
42956     onMenuHide: function(e) {
42957         var me = this;
42958         me.removeClsWithUI(me.menuActiveCls);
42959         me.ignoreNextClick = Ext.defer(me.restoreClick, 250, me);
42960         me.fireEvent('menuhide', me, me.menu);
42961     },
42962
42963     // private
42964     restoreClick: function() {
42965         this.ignoreNextClick = 0;
42966     },
42967
42968     // private
42969     onDownKey: function() {
42970         var me = this;
42971
42972         if (!me.disabled) {
42973             if (me.menu) {
42974                 me.showMenu();
42975             }
42976         }
42977     },
42978
42979     /**
42980      * @private Some browsers (notably Safari and older Chromes on Windows) add extra "padding" inside the button
42981      * element that cannot be removed. This method returns the size of that padding with a one-time detection.
42982      * @return Array [top, right, bottom, left]
42983      */
42984     getPersistentBtnPadding: function() {
42985         var cls = Ext.button.Button,
42986             padding = cls.persistentPadding,
42987             btn, leftTop, btnEl, btnInnerEl;
42988
42989         if (!padding) {
42990             padding = cls.persistentPadding = [0, 0, 0, 0]; //set early to prevent recursion
42991
42992             if (!Ext.isIE) { //short-circuit IE as it sometimes gives false positive for padding
42993                 // Create auto-size button offscreen and measure its insides
42994                 btn = Ext.create('Ext.button.Button', {
42995                     renderTo: Ext.getBody(),
42996                     text: 'test',
42997                     style: 'position:absolute;top:-999px;'
42998                 });
42999                 btnEl = btn.btnEl;
43000                 btnInnerEl = btn.btnInnerEl;
43001                 btnEl.setSize(null, null); //clear any hard dimensions on the button el to see what it does naturally
43002
43003                 leftTop = btnInnerEl.getOffsetsTo(btnEl);
43004                 padding[0] = leftTop[1];
43005                 padding[1] = btnEl.getWidth() - btnInnerEl.getWidth() - leftTop[0];
43006                 padding[2] = btnEl.getHeight() - btnInnerEl.getHeight() - leftTop[1];
43007                 padding[3] = leftTop[0];
43008
43009                 btn.destroy();
43010             }
43011         }
43012
43013         return padding;
43014     }
43015
43016 }, function() {
43017     var groups = {},
43018         g, i, l;
43019
43020     function toggleGroup(btn, state) {
43021         if (state) {
43022             g = groups[btn.toggleGroup];
43023             for (i = 0, l = g.length; i < l; i++) {
43024                 if (g[i] !== btn) {
43025                     g[i].toggle(false);
43026                 }
43027             }
43028         }
43029     }
43030     // Private utility class used by Button
43031     Ext.ButtonToggleManager = {
43032         register: function(btn) {
43033             if (!btn.toggleGroup) {
43034                 return;
43035             }
43036             var group = groups[btn.toggleGroup];
43037             if (!group) {
43038                 group = groups[btn.toggleGroup] = [];
43039             }
43040             group.push(btn);
43041             btn.on('toggle', toggleGroup);
43042         },
43043
43044         unregister: function(btn) {
43045             if (!btn.toggleGroup) {
43046                 return;
43047             }
43048             var group = groups[btn.toggleGroup];
43049             if (group) {
43050                 Ext.Array.remove(group, btn);
43051                 btn.un('toggle', toggleGroup);
43052             }
43053         },
43054
43055         /**
43056         * Gets the pressed button in the passed group or null
43057         * @param {String} group
43058         * @return Button
43059         */
43060         getPressed: function(group) {
43061             var g = groups[group],
43062                 i = 0,
43063                 len;
43064             if (g) {
43065                 for (len = g.length; i < len; i++) {
43066                     if (g[i].pressed === true) {
43067                         return g[i];
43068                     }
43069                 }
43070             }
43071             return null;
43072         }
43073     };
43074 });
43075
43076 /**
43077  * @class Ext.layout.container.boxOverflow.Menu
43078  * @extends Ext.layout.container.boxOverflow.None
43079  * @private
43080  */
43081 Ext.define('Ext.layout.container.boxOverflow.Menu', {
43082
43083     /* Begin Definitions */
43084
43085     extend: 'Ext.layout.container.boxOverflow.None',
43086     requires: ['Ext.toolbar.Separator', 'Ext.button.Button'],
43087     alternateClassName: 'Ext.layout.boxOverflow.Menu',
43088     
43089     /* End Definitions */
43090
43091     /**
43092      * @cfg {String} afterCtCls
43093      * CSS class added to the afterCt element. This is the element that holds any special items such as scrollers,
43094      * which must always be present at the rightmost edge of the Container
43095      */
43096
43097     /**
43098      * @property noItemsMenuText
43099      * @type String
43100      * HTML fragment to render into the toolbar overflow menu if there are no items to display
43101      */
43102     noItemsMenuText : '<div class="' + Ext.baseCSSPrefix + 'toolbar-no-items">(None)</div>',
43103
43104     constructor: function(layout) {
43105         var me = this;
43106
43107         me.callParent(arguments);
43108
43109         // Before layout, we need to re-show all items which we may have hidden due to a previous overflow.
43110         layout.beforeLayout = Ext.Function.createInterceptor(layout.beforeLayout, this.clearOverflow, this);
43111
43112         me.afterCtCls = me.afterCtCls || Ext.baseCSSPrefix + 'box-menu-' + layout.parallelAfter;
43113         /**
43114          * @property menuItems
43115          * @type Array
43116          * Array of all items that are currently hidden and should go into the dropdown menu
43117          */
43118         me.menuItems = [];
43119     },
43120
43121     handleOverflow: function(calculations, targetSize) {
43122         var me = this,
43123             layout = me.layout,
43124             methodName = 'get' + layout.parallelPrefixCap,
43125             newSize = {},
43126             posArgs = [null, null];
43127
43128         me.callParent(arguments);
43129         this.createMenu(calculations, targetSize);
43130         newSize[layout.perpendicularPrefix] = targetSize[layout.perpendicularPrefix];
43131         newSize[layout.parallelPrefix] = targetSize[layout.parallelPrefix] - me.afterCt[methodName]();
43132
43133         // Center the menuTrigger button.
43134         // TODO: Should we emulate align: 'middle' like this, or should we 'stretchmax' the menuTrigger?
43135         posArgs[layout.perpendicularSizeIndex] = (calculations.meta.maxSize - me.menuTrigger['get' + layout.perpendicularPrefixCap]()) / 2;
43136         me.menuTrigger.setPosition.apply(me.menuTrigger, posArgs);
43137
43138         return { targetSize: newSize };
43139     },
43140
43141     /**
43142      * @private
43143      * Called by the layout, when it determines that there is no overflow.
43144      * Also called as an interceptor to the layout's onLayout method to reshow
43145      * previously hidden overflowing items.
43146      */
43147     clearOverflow: function(calculations, targetSize) {
43148         var me = this,
43149             newWidth = targetSize ? targetSize.width + (me.afterCt ? me.afterCt.getWidth() : 0) : 0,
43150             items = me.menuItems,
43151             i = 0,
43152             length = items.length,
43153             item;
43154
43155         me.hideTrigger();
43156         for (; i < length; i++) {
43157             items[i].show();
43158         }
43159         items.length = 0;
43160
43161         return targetSize ? {
43162             targetSize: {
43163                 height: targetSize.height,
43164                 width : newWidth
43165             }
43166         } : null;
43167     },
43168
43169     /**
43170      * @private
43171      */
43172     showTrigger: function() {
43173         this.menuTrigger.show();
43174     },
43175
43176     /**
43177      * @private
43178      */
43179     hideTrigger: function() {
43180         if (this.menuTrigger != undefined) {
43181             this.menuTrigger.hide();
43182         }
43183     },
43184
43185     /**
43186      * @private
43187      * Called before the overflow menu is shown. This constructs the menu's items, caching them for as long as it can.
43188      */
43189     beforeMenuShow: function(menu) {
43190         var me = this,
43191             items = me.menuItems,
43192             i = 0,
43193             len   = items.length,
43194             item,
43195             prev;
43196
43197         var needsSep = function(group, prev){
43198             return group.isXType('buttongroup') && !(prev instanceof Ext.toolbar.Separator);
43199         };
43200
43201         me.clearMenu();
43202         menu.removeAll();
43203
43204         for (; i < len; i++) {
43205             item = items[i];
43206
43207             // Do not show a separator as a first item
43208             if (!i && (item instanceof Ext.toolbar.Separator)) {
43209                 continue;
43210             }
43211             if (prev && (needsSep(item, prev) || needsSep(prev, item))) {
43212                 menu.add('-');
43213             }
43214
43215             me.addComponentToMenu(menu, item);
43216             prev = item;
43217         }
43218
43219         // put something so the menu isn't empty if no compatible items found
43220         if (menu.items.length < 1) {
43221             menu.add(me.noItemsMenuText);
43222         }
43223     },
43224     
43225     /**
43226      * @private
43227      * Returns a menu config for a given component. This config is used to create a menu item
43228      * to be added to the expander menu
43229      * @param {Ext.Component} component The component to create the config for
43230      * @param {Boolean} hideOnClick Passed through to the menu item
43231      */
43232     createMenuConfig : function(component, hideOnClick) {
43233         var config = Ext.apply({}, component.initialConfig),
43234             group  = component.toggleGroup;
43235
43236         Ext.copyTo(config, component, [
43237             'iconCls', 'icon', 'itemId', 'disabled', 'handler', 'scope', 'menu'
43238         ]);
43239
43240         Ext.apply(config, {
43241             text       : component.overflowText || component.text,
43242             hideOnClick: hideOnClick,
43243             destroyMenu: false
43244         });
43245
43246         if (group || component.enableToggle) {
43247             Ext.apply(config, {
43248                 group  : group,
43249                 checked: component.pressed,
43250                 listeners: {
43251                     checkchange: function(item, checked){
43252                         component.toggle(checked);
43253                     }
43254                 }
43255             });
43256         }
43257
43258         delete config.ownerCt;
43259         delete config.xtype;
43260         delete config.id;
43261         return config;
43262     },
43263
43264     /**
43265      * @private
43266      * Adds the given Toolbar item to the given menu. Buttons inside a buttongroup are added individually.
43267      * @param {Ext.menu.Menu} menu The menu to add to
43268      * @param {Ext.Component} component The component to add
43269      */
43270     addComponentToMenu : function(menu, component) {
43271         var me = this;
43272         if (component instanceof Ext.toolbar.Separator) {
43273             menu.add('-');
43274         } else if (component.isComponent) {
43275             if (component.isXType('splitbutton')) {
43276                 menu.add(me.createMenuConfig(component, true));
43277
43278             } else if (component.isXType('button')) {
43279                 menu.add(me.createMenuConfig(component, !component.menu));
43280
43281             } else if (component.isXType('buttongroup')) {
43282                 component.items.each(function(item){
43283                      me.addComponentToMenu(menu, item);
43284                 });
43285             } else {
43286                 menu.add(Ext.create(Ext.getClassName(component), me.createMenuConfig(component)));
43287             }
43288         }
43289     },
43290
43291     /**
43292      * @private
43293      * Deletes the sub-menu of each item in the expander menu. Submenus are created for items such as
43294      * splitbuttons and buttongroups, where the Toolbar item cannot be represented by a single menu item
43295      */
43296     clearMenu : function() {
43297         var menu = this.moreMenu;
43298         if (menu && menu.items) {
43299             menu.items.each(function(item) {
43300                 if (item.menu) {
43301                     delete item.menu;
43302                 }
43303             });
43304         }
43305     },
43306
43307     /**
43308      * @private
43309      * Creates the overflow trigger and menu used when enableOverflow is set to true and the items
43310      * in the layout are too wide to fit in the space available
43311      */
43312     createMenu: function(calculations, targetSize) {
43313         var me = this,
43314             layout = me.layout,
43315             startProp = layout.parallelBefore,
43316             sizeProp = layout.parallelPrefix,
43317             available = targetSize[sizeProp],
43318             boxes = calculations.boxes,
43319             i = 0,
43320             len = boxes.length,
43321             box;
43322
43323         if (!me.menuTrigger) {
43324             me.createInnerElements();
43325
43326             /**
43327              * @private
43328              * @property menu
43329              * @type Ext.menu.Menu
43330              * The expand menu - holds items for every item that cannot be shown
43331              * because the container is currently not large enough.
43332              */
43333             me.menu = Ext.create('Ext.menu.Menu', {
43334                 hideMode: 'offsets',
43335                 listeners: {
43336                     scope: me,
43337                     beforeshow: me.beforeMenuShow
43338                 }
43339             });
43340
43341             /**
43342              * @private
43343              * @property menuTrigger
43344              * @type Ext.button.Button
43345              * The expand button which triggers the overflow menu to be shown
43346              */
43347             me.menuTrigger = Ext.create('Ext.button.Button', {
43348                 ownerCt : me.layout.owner, // To enable the Menu to ascertain a valid zIndexManager owner in the same tree
43349                 iconCls : Ext.baseCSSPrefix + layout.owner.getXType() + '-more-icon',
43350                 ui      : layout.owner instanceof Ext.toolbar.Toolbar ? 'default-toolbar' : 'default',
43351                 menu    : me.menu,
43352                 getSplitCls: function() { return '';},
43353                 renderTo: me.afterCt
43354             });
43355         }
43356         me.showTrigger();
43357         available -= me.afterCt.getWidth();
43358
43359         // Hide all items which are off the end, and store them to allow them to be restored
43360         // before each layout operation.
43361         me.menuItems.length = 0;
43362         for (; i < len; i++) {
43363             box = boxes[i];
43364             if (box[startProp] + box[sizeProp] > available) {
43365                 me.menuItems.push(box.component);
43366                 box.component.hide();
43367             }
43368         }
43369     },
43370
43371     /**
43372      * @private
43373      * Creates the beforeCt, innerCt and afterCt elements if they have not already been created
43374      * @param {Ext.container.Container} container The Container attached to this Layout instance
43375      * @param {Ext.core.Element} target The target Element
43376      */
43377     createInnerElements: function() {
43378         var me = this,
43379             target = me.layout.getRenderTarget();
43380
43381         if (!this.afterCt) {
43382             target.addCls(Ext.baseCSSPrefix + me.layout.direction + '-box-overflow-body');
43383             this.afterCt  = target.insertSibling({cls: Ext.layout.container.Box.prototype.innerCls + ' ' + this.afterCtCls}, 'before');
43384         }
43385     },
43386
43387     /**
43388      * @private
43389      */
43390     destroy: function() {
43391         Ext.destroy(this.menu, this.menuTrigger);
43392     }
43393 });
43394 /**
43395  * @class Ext.util.Region
43396  * @extends Object
43397  *
43398  * Represents a rectangular region and provides a number of utility methods
43399  * to compare regions.
43400  */
43401
43402 Ext.define('Ext.util.Region', {
43403
43404     /* Begin Definitions */
43405
43406     requires: ['Ext.util.Offset'],
43407
43408     statics: {
43409         /**
43410          * @static
43411          * @param {Mixed} el A string, DomElement or Ext.core.Element representing an element
43412          * on the page.
43413          * @returns {Ext.util.Region} region
43414          * Retrieves an Ext.util.Region for a particular element.
43415          */
43416         getRegion: function(el) {
43417             return Ext.fly(el).getPageBox(true);
43418         },
43419
43420         /**
43421          * @static
43422          * @param {Object} o An object with top, right, bottom, left properties
43423          * @return {Ext.util.Region} region The region constructed based on the passed object
43424          */
43425         from: function(o) {
43426             return new this(o.top, o.right, o.bottom, o.left);
43427         }
43428     },
43429
43430     /* End Definitions */
43431
43432     /**
43433      * @constructor
43434      * @param {Number} top Top
43435      * @param {Number} right Right
43436      * @param {Number} bottom Bottom
43437      * @param {Number} left Left
43438      */
43439     constructor : function(t, r, b, l) {
43440         var me = this;
43441         me.y = me.top = me[1] = t;
43442         me.right = r;
43443         me.bottom = b;
43444         me.x = me.left = me[0] = l;
43445     },
43446
43447     /**
43448      * Checks if this region completely contains the region that is passed in.
43449      * @param {Ext.util.Region} region
43450      */
43451     contains : function(region) {
43452         var me = this;
43453         return (region.x >= me.x &&
43454                 region.right <= me.right &&
43455                 region.y >= me.y &&
43456                 region.bottom <= me.bottom);
43457
43458     },
43459
43460     /**
43461      * Checks if this region intersects the region passed in.
43462      * @param {Ext.util.Region} region
43463      * @return {Ext.util.Region/Boolean} Returns the intersected region or false if there is no intersection.
43464      */
43465     intersect : function(region) {
43466         var me = this,
43467             t = Math.max(me.y, region.y),
43468             r = Math.min(me.right, region.right),
43469             b = Math.min(me.bottom, region.bottom),
43470             l = Math.max(me.x, region.x);
43471
43472         if (b > t && r > l) {
43473             return new this.self(t, r, b, l);
43474         }
43475         else {
43476             return false;
43477         }
43478     },
43479
43480     /**
43481      * Returns the smallest region that contains the current AND targetRegion.
43482      * @param {Ext.util.Region} region
43483      */
43484     union : function(region) {
43485         var me = this,
43486             t = Math.min(me.y, region.y),
43487             r = Math.max(me.right, region.right),
43488             b = Math.max(me.bottom, region.bottom),
43489             l = Math.min(me.x, region.x);
43490
43491         return new this.self(t, r, b, l);
43492     },
43493
43494     /**
43495      * Modifies the current region to be constrained to the targetRegion.
43496      * @param {Ext.util.Region} targetRegion
43497      */
43498     constrainTo : function(r) {
43499         var me = this,
43500             constrain = Ext.Number.constrain;
43501         me.top = me.y = constrain(me.top, r.y, r.bottom);
43502         me.bottom = constrain(me.bottom, r.y, r.bottom);
43503         me.left = me.x = constrain(me.left, r.x, r.right);
43504         me.right = constrain(me.right, r.x, r.right);
43505         return me;
43506     },
43507
43508     /**
43509      * Modifies the current region to be adjusted by offsets.
43510      * @param {Number} top top offset
43511      * @param {Number} right right offset
43512      * @param {Number} bottom bottom offset
43513      * @param {Number} left left offset
43514      */
43515     adjust : function(t, r, b, l) {
43516         var me = this;
43517         me.top = me.y += t;
43518         me.left = me.x += l;
43519         me.right += r;
43520         me.bottom += b;
43521         return me;
43522     },
43523
43524     /**
43525      * Get the offset amount of a point outside the region
43526      * @param {String} axis optional
43527      * @param {Ext.util.Point} p the point
43528      * @return {Ext.util.Offset}
43529      */
43530     getOutOfBoundOffset: function(axis, p) {
43531         if (!Ext.isObject(axis)) {
43532             if (axis == 'x') {
43533                 return this.getOutOfBoundOffsetX(p);
43534             } else {
43535                 return this.getOutOfBoundOffsetY(p);
43536             }
43537         } else {
43538             p = axis;
43539             var d = Ext.create('Ext.util.Offset');
43540             d.x = this.getOutOfBoundOffsetX(p.x);
43541             d.y = this.getOutOfBoundOffsetY(p.y);
43542             return d;
43543         }
43544
43545     },
43546
43547     /**
43548      * Get the offset amount on the x-axis
43549      * @param {Number} p the offset
43550      * @return {Number}
43551      */
43552     getOutOfBoundOffsetX: function(p) {
43553         if (p <= this.x) {
43554             return this.x - p;
43555         } else if (p >= this.right) {
43556             return this.right - p;
43557         }
43558
43559         return 0;
43560     },
43561
43562     /**
43563      * Get the offset amount on the y-axis
43564      * @param {Number} p the offset
43565      * @return {Number}
43566      */
43567     getOutOfBoundOffsetY: function(p) {
43568         if (p <= this.y) {
43569             return this.y - p;
43570         } else if (p >= this.bottom) {
43571             return this.bottom - p;
43572         }
43573
43574         return 0;
43575     },
43576
43577     /**
43578      * Check whether the point / offset is out of bound
43579      * @param {String} axis optional
43580      * @param {Ext.util.Point/Number} p the point / offset
43581      * @return {Boolean}
43582      */
43583     isOutOfBound: function(axis, p) {
43584         if (!Ext.isObject(axis)) {
43585             if (axis == 'x') {
43586                 return this.isOutOfBoundX(p);
43587             } else {
43588                 return this.isOutOfBoundY(p);
43589             }
43590         } else {
43591             p = axis;
43592             return (this.isOutOfBoundX(p.x) || this.isOutOfBoundY(p.y));
43593         }
43594     },
43595
43596     /**
43597      * Check whether the offset is out of bound in the x-axis
43598      * @param {Number} p the offset
43599      * @return {Boolean}
43600      */
43601     isOutOfBoundX: function(p) {
43602         return (p < this.x || p > this.right);
43603     },
43604
43605     /**
43606      * Check whether the offset is out of bound in the y-axis
43607      * @param {Number} p the offset
43608      * @return {Boolean}
43609      */
43610     isOutOfBoundY: function(p) {
43611         return (p < this.y || p > this.bottom);
43612     },
43613
43614     /*
43615      * Restrict a point within the region by a certain factor.
43616      * @param {String} axis Optional
43617      * @param {Ext.util.Point/Ext.util.Offset/Object} p
43618      * @param {Number} factor
43619      * @return {Ext.util.Point/Ext.util.Offset/Object/Number}
43620      */
43621     restrict: function(axis, p, factor) {
43622         if (Ext.isObject(axis)) {
43623             var newP;
43624
43625             factor = p;
43626             p = axis;
43627
43628             if (p.copy) {
43629                 newP = p.copy();
43630             }
43631             else {
43632                 newP = {
43633                     x: p.x,
43634                     y: p.y
43635                 };
43636             }
43637
43638             newP.x = this.restrictX(p.x, factor);
43639             newP.y = this.restrictY(p.y, factor);
43640             return newP;
43641         } else {
43642             if (axis == 'x') {
43643                 return this.restrictX(p, factor);
43644             } else {
43645                 return this.restrictY(p, factor);
43646             }
43647         }
43648     },
43649
43650     /*
43651      * Restrict an offset within the region by a certain factor, on the x-axis
43652      * @param {Number} p
43653      * @param {Number} factor The factor, optional, defaults to 1
43654      * @return
43655      */
43656     restrictX : function(p, factor) {
43657         if (!factor) {
43658             factor = 1;
43659         }
43660
43661         if (p <= this.x) {
43662             p -= (p - this.x) * factor;
43663         }
43664         else if (p >= this.right) {
43665             p -= (p - this.right) * factor;
43666         }
43667         return p;
43668     },
43669
43670     /*
43671      * Restrict an offset within the region by a certain factor, on the y-axis
43672      * @param {Number} p
43673      * @param {Number} factor The factor, optional, defaults to 1
43674      */
43675     restrictY : function(p, factor) {
43676         if (!factor) {
43677             factor = 1;
43678         }
43679
43680         if (p <= this.y) {
43681             p -= (p - this.y) * factor;
43682         }
43683         else if (p >= this.bottom) {
43684             p -= (p - this.bottom) * factor;
43685         }
43686         return p;
43687     },
43688
43689     /*
43690      * Get the width / height of this region
43691      * @return {Object} an object with width and height properties
43692      */
43693     getSize: function() {
43694         return {
43695             width: this.right - this.x,
43696             height: this.bottom - this.y
43697         };
43698     },
43699
43700     /**
43701      * Copy a new instance
43702      * @return {Ext.util.Region}
43703      */
43704     copy: function() {
43705         return new this.self(this.y, this.right, this.bottom, this.x);
43706     },
43707
43708     /**
43709      * Copy the values of another Region to this Region
43710      * @param {Region} The region to copy from.
43711      * @return {Ext.util.Point} this This point
43712      */
43713     copyFrom: function(p) {
43714         var me = this;
43715         me.top = me.y = me[1] = p.y;
43716         me.right = p.right;
43717         me.bottom = p.bottom;
43718         me.left = me.x = me[0] = p.x;
43719
43720         return this;
43721     },
43722
43723     /**
43724      * Dump this to an eye-friendly string, great for debugging
43725      * @return {String}
43726      */
43727     toString: function() {
43728         return "Region[" + this.top + "," + this.right + "," + this.bottom + "," + this.left + "]";
43729     },
43730
43731
43732     /**
43733      * Translate this region by the given offset amount
43734      * @param {Ext.util.Offset/Object} offset Object containing the <code>x</code> and <code>y</code> properties.
43735      * Or the x value is using the two argument form.
43736      * @param {Number} The y value unless using an Offset object.
43737      * @return {Ext.util.Region} this This Region
43738      */
43739     translateBy: function(x, y) {
43740         if (arguments.length == 1) {
43741             y = x.y;
43742             x = x.x;
43743         }
43744         var me = this;
43745         me.top = me.y += y;
43746         me.right += x;
43747         me.bottom += y;
43748         me.left = me.x += x;
43749
43750         return me;
43751     },
43752
43753     /**
43754      * Round all the properties of this region
43755      * @return {Ext.util.Region} this This Region
43756      */
43757     round: function() {
43758         var me = this;
43759         me.top = me.y = Math.round(me.y);
43760         me.right = Math.round(me.right);
43761         me.bottom = Math.round(me.bottom);
43762         me.left = me.x = Math.round(me.x);
43763
43764         return me;
43765     },
43766
43767     /**
43768      * Check whether this region is equivalent to the given region
43769      * @param {Ext.util.Region} region The region to compare with
43770      * @return {Boolean}
43771      */
43772     equals: function(region) {
43773         return (this.top == region.top && this.right == region.right && this.bottom == region.bottom && this.left == region.left);
43774     }
43775 });
43776
43777 /*
43778  * This is a derivative of the similarly named class in the YUI Library.
43779  * The original license:
43780  * Copyright (c) 2006, Yahoo! Inc. All rights reserved.
43781  * Code licensed under the BSD License:
43782  * http://developer.yahoo.net/yui/license.txt
43783  */
43784
43785
43786 /**
43787  * @class Ext.dd.DragDropManager
43788  * DragDropManager is a singleton that tracks the element interaction for
43789  * all DragDrop items in the window.  Generally, you will not call
43790  * this class directly, but it does have helper methods that could
43791  * be useful in your DragDrop implementations.
43792  * @singleton
43793  */
43794 Ext.define('Ext.dd.DragDropManager', {
43795     singleton: true,
43796
43797     requires: ['Ext.util.Region'],
43798
43799     uses: ['Ext.tip.QuickTipManager'],
43800
43801     // shorter ClassName, to save bytes and use internally
43802     alternateClassName: ['Ext.dd.DragDropMgr', 'Ext.dd.DDM'],
43803     
43804     /**
43805      * Two dimensional Array of registered DragDrop objects.  The first
43806      * dimension is the DragDrop item group, the second the DragDrop
43807      * object.
43808      * @property ids
43809      * @type String[]
43810      * @private
43811      * @static
43812      */
43813     ids: {},
43814
43815     /**
43816      * Array of element ids defined as drag handles.  Used to determine
43817      * if the element that generated the mousedown event is actually the
43818      * handle and not the html element itself.
43819      * @property handleIds
43820      * @type String[]
43821      * @private
43822      * @static
43823      */
43824     handleIds: {},
43825
43826     /**
43827      * the DragDrop object that is currently being dragged
43828      * @property dragCurrent
43829      * @type DragDrop
43830      * @private
43831      * @static
43832      **/
43833     dragCurrent: null,
43834
43835     /**
43836      * the DragDrop object(s) that are being hovered over
43837      * @property dragOvers
43838      * @type Array
43839      * @private
43840      * @static
43841      */
43842     dragOvers: {},
43843
43844     /**
43845      * the X distance between the cursor and the object being dragged
43846      * @property deltaX
43847      * @type int
43848      * @private
43849      * @static
43850      */
43851     deltaX: 0,
43852
43853     /**
43854      * the Y distance between the cursor and the object being dragged
43855      * @property deltaY
43856      * @type int
43857      * @private
43858      * @static
43859      */
43860     deltaY: 0,
43861
43862     /**
43863      * Flag to determine if we should prevent the default behavior of the
43864      * events we define. By default this is true, but this can be set to
43865      * false if you need the default behavior (not recommended)
43866      * @property preventDefault
43867      * @type boolean
43868      * @static
43869      */
43870     preventDefault: true,
43871
43872     /**
43873      * Flag to determine if we should stop the propagation of the events
43874      * we generate. This is true by default but you may want to set it to
43875      * false if the html element contains other features that require the
43876      * mouse click.
43877      * @property stopPropagation
43878      * @type boolean
43879      * @static
43880      */
43881     stopPropagation: true,
43882
43883     /**
43884      * Internal flag that is set to true when drag and drop has been
43885      * intialized
43886      * @property initialized
43887      * @private
43888      * @static
43889      */
43890     initialized: false,
43891
43892     /**
43893      * All drag and drop can be disabled.
43894      * @property locked
43895      * @private
43896      * @static
43897      */
43898     locked: false,
43899
43900     /**
43901      * Called the first time an element is registered.
43902      * @method init
43903      * @private
43904      * @static
43905      */
43906     init: function() {
43907         this.initialized = true;
43908     },
43909
43910     /**
43911      * In point mode, drag and drop interaction is defined by the
43912      * location of the cursor during the drag/drop
43913      * @property POINT
43914      * @type int
43915      * @static
43916      */
43917     POINT: 0,
43918
43919     /**
43920      * In intersect mode, drag and drop interaction is defined by the
43921      * overlap of two or more drag and drop objects.
43922      * @property INTERSECT
43923      * @type int
43924      * @static
43925      */
43926     INTERSECT: 1,
43927
43928     /**
43929      * The current drag and drop mode.  Default: POINT
43930      * @property mode
43931      * @type int
43932      * @static
43933      */
43934     mode: 0,
43935
43936     /**
43937      * Runs method on all drag and drop objects
43938      * @method _execOnAll
43939      * @private
43940      * @static
43941      */
43942     _execOnAll: function(sMethod, args) {
43943         for (var i in this.ids) {
43944             for (var j in this.ids[i]) {
43945                 var oDD = this.ids[i][j];
43946                 if (! this.isTypeOfDD(oDD)) {
43947                     continue;
43948                 }
43949                 oDD[sMethod].apply(oDD, args);
43950             }
43951         }
43952     },
43953
43954     /**
43955      * Drag and drop initialization.  Sets up the global event handlers
43956      * @method _onLoad
43957      * @private
43958      * @static
43959      */
43960     _onLoad: function() {
43961
43962         this.init();
43963
43964         var Event = Ext.EventManager;
43965         Event.on(document, "mouseup",   this.handleMouseUp, this, true);
43966         Event.on(document, "mousemove", this.handleMouseMove, this, true);
43967         Event.on(window,   "unload",    this._onUnload, this, true);
43968         Event.on(window,   "resize",    this._onResize, this, true);
43969         // Event.on(window,   "mouseout",    this._test);
43970
43971     },
43972
43973     /**
43974      * Reset constraints on all drag and drop objs
43975      * @method _onResize
43976      * @private
43977      * @static
43978      */
43979     _onResize: function(e) {
43980         this._execOnAll("resetConstraints", []);
43981     },
43982
43983     /**
43984      * Lock all drag and drop functionality
43985      * @method lock
43986      * @static
43987      */
43988     lock: function() { this.locked = true; },
43989
43990     /**
43991      * Unlock all drag and drop functionality
43992      * @method unlock
43993      * @static
43994      */
43995     unlock: function() { this.locked = false; },
43996
43997     /**
43998      * Is drag and drop locked?
43999      * @method isLocked
44000      * @return {boolean} True if drag and drop is locked, false otherwise.
44001      * @static
44002      */
44003     isLocked: function() { return this.locked; },
44004
44005     /**
44006      * Location cache that is set for all drag drop objects when a drag is
44007      * initiated, cleared when the drag is finished.
44008      * @property locationCache
44009      * @private
44010      * @static
44011      */
44012     locationCache: {},
44013
44014     /**
44015      * Set useCache to false if you want to force object the lookup of each
44016      * drag and drop linked element constantly during a drag.
44017      * @property useCache
44018      * @type boolean
44019      * @static
44020      */
44021     useCache: true,
44022
44023     /**
44024      * The number of pixels that the mouse needs to move after the
44025      * mousedown before the drag is initiated.  Default=3;
44026      * @property clickPixelThresh
44027      * @type int
44028      * @static
44029      */
44030     clickPixelThresh: 3,
44031
44032     /**
44033      * The number of milliseconds after the mousedown event to initiate the
44034      * drag if we don't get a mouseup event. Default=350
44035      * @property clickTimeThresh
44036      * @type int
44037      * @static
44038      */
44039     clickTimeThresh: 350,
44040
44041     /**
44042      * Flag that indicates that either the drag pixel threshold or the
44043      * mousdown time threshold has been met
44044      * @property dragThreshMet
44045      * @type boolean
44046      * @private
44047      * @static
44048      */
44049     dragThreshMet: false,
44050
44051     /**
44052      * Timeout used for the click time threshold
44053      * @property clickTimeout
44054      * @type Object
44055      * @private
44056      * @static
44057      */
44058     clickTimeout: null,
44059
44060     /**
44061      * The X position of the mousedown event stored for later use when a
44062      * drag threshold is met.
44063      * @property startX
44064      * @type int
44065      * @private
44066      * @static
44067      */
44068     startX: 0,
44069
44070     /**
44071      * The Y position of the mousedown event stored for later use when a
44072      * drag threshold is met.
44073      * @property startY
44074      * @type int
44075      * @private
44076      * @static
44077      */
44078     startY: 0,
44079
44080     /**
44081      * Each DragDrop instance must be registered with the DragDropManager.
44082      * This is executed in DragDrop.init()
44083      * @method regDragDrop
44084      * @param {DragDrop} oDD the DragDrop object to register
44085      * @param {String} sGroup the name of the group this element belongs to
44086      * @static
44087      */
44088     regDragDrop: function(oDD, sGroup) {
44089         if (!this.initialized) { this.init(); }
44090
44091         if (!this.ids[sGroup]) {
44092             this.ids[sGroup] = {};
44093         }
44094         this.ids[sGroup][oDD.id] = oDD;
44095     },
44096
44097     /**
44098      * Removes the supplied dd instance from the supplied group. Executed
44099      * by DragDrop.removeFromGroup, so don't call this function directly.
44100      * @method removeDDFromGroup
44101      * @private
44102      * @static
44103      */
44104     removeDDFromGroup: function(oDD, sGroup) {
44105         if (!this.ids[sGroup]) {
44106             this.ids[sGroup] = {};
44107         }
44108
44109         var obj = this.ids[sGroup];
44110         if (obj && obj[oDD.id]) {
44111             delete obj[oDD.id];
44112         }
44113     },
44114
44115     /**
44116      * Unregisters a drag and drop item.  This is executed in
44117      * DragDrop.unreg, use that method instead of calling this directly.
44118      * @method _remove
44119      * @private
44120      * @static
44121      */
44122     _remove: function(oDD) {
44123         for (var g in oDD.groups) {
44124             if (g && this.ids[g] && this.ids[g][oDD.id]) {
44125                 delete this.ids[g][oDD.id];
44126             }
44127         }
44128         delete this.handleIds[oDD.id];
44129     },
44130
44131     /**
44132      * Each DragDrop handle element must be registered.  This is done
44133      * automatically when executing DragDrop.setHandleElId()
44134      * @method regHandle
44135      * @param {String} sDDId the DragDrop id this element is a handle for
44136      * @param {String} sHandleId the id of the element that is the drag
44137      * handle
44138      * @static
44139      */
44140     regHandle: function(sDDId, sHandleId) {
44141         if (!this.handleIds[sDDId]) {
44142             this.handleIds[sDDId] = {};
44143         }
44144         this.handleIds[sDDId][sHandleId] = sHandleId;
44145     },
44146
44147     /**
44148      * Utility function to determine if a given element has been
44149      * registered as a drag drop item.
44150      * @method isDragDrop
44151      * @param {String} id the element id to check
44152      * @return {boolean} true if this element is a DragDrop item,
44153      * false otherwise
44154      * @static
44155      */
44156     isDragDrop: function(id) {
44157         return ( this.getDDById(id) ) ? true : false;
44158     },
44159
44160     /**
44161      * Returns the drag and drop instances that are in all groups the
44162      * passed in instance belongs to.
44163      * @method getRelated
44164      * @param {DragDrop} p_oDD the obj to get related data for
44165      * @param {boolean} bTargetsOnly if true, only return targetable objs
44166      * @return {DragDrop[]} the related instances
44167      * @static
44168      */
44169     getRelated: function(p_oDD, bTargetsOnly) {
44170         var oDDs = [];
44171         for (var i in p_oDD.groups) {
44172             for (var j in this.ids[i]) {
44173                 var dd = this.ids[i][j];
44174                 if (! this.isTypeOfDD(dd)) {
44175                     continue;
44176                 }
44177                 if (!bTargetsOnly || dd.isTarget) {
44178                     oDDs[oDDs.length] = dd;
44179                 }
44180             }
44181         }
44182
44183         return oDDs;
44184     },
44185
44186     /**
44187      * Returns true if the specified dd target is a legal target for
44188      * the specifice drag obj
44189      * @method isLegalTarget
44190      * @param {DragDrop} oDD the drag obj
44191      * @param {DragDrop} oTargetDD the target
44192      * @return {boolean} true if the target is a legal target for the
44193      * dd obj
44194      * @static
44195      */
44196     isLegalTarget: function (oDD, oTargetDD) {
44197         var targets = this.getRelated(oDD, true);
44198         for (var i=0, len=targets.length;i<len;++i) {
44199             if (targets[i].id == oTargetDD.id) {
44200                 return true;
44201             }
44202         }
44203
44204         return false;
44205     },
44206
44207     /**
44208      * My goal is to be able to transparently determine if an object is
44209      * typeof DragDrop, and the exact subclass of DragDrop.  typeof
44210      * returns "object", oDD.constructor.toString() always returns
44211      * "DragDrop" and not the name of the subclass.  So for now it just
44212      * evaluates a well-known variable in DragDrop.
44213      * @method isTypeOfDD
44214      * @param {Object} the object to evaluate
44215      * @return {boolean} true if typeof oDD = DragDrop
44216      * @static
44217      */
44218     isTypeOfDD: function (oDD) {
44219         return (oDD && oDD.__ygDragDrop);
44220     },
44221
44222     /**
44223      * Utility function to determine if a given element has been
44224      * registered as a drag drop handle for the given Drag Drop object.
44225      * @method isHandle
44226      * @param {String} id the element id to check
44227      * @return {boolean} true if this element is a DragDrop handle, false
44228      * otherwise
44229      * @static
44230      */
44231     isHandle: function(sDDId, sHandleId) {
44232         return ( this.handleIds[sDDId] &&
44233                         this.handleIds[sDDId][sHandleId] );
44234     },
44235
44236     /**
44237      * Returns the DragDrop instance for a given id
44238      * @method getDDById
44239      * @param {String} id the id of the DragDrop object
44240      * @return {DragDrop} the drag drop object, null if it is not found
44241      * @static
44242      */
44243     getDDById: function(id) {
44244         for (var i in this.ids) {
44245             if (this.ids[i][id]) {
44246                 return this.ids[i][id];
44247             }
44248         }
44249         return null;
44250     },
44251
44252     /**
44253      * Fired after a registered DragDrop object gets the mousedown event.
44254      * Sets up the events required to track the object being dragged
44255      * @method handleMouseDown
44256      * @param {Event} e the event
44257      * @param oDD the DragDrop object being dragged
44258      * @private
44259      * @static
44260      */
44261     handleMouseDown: function(e, oDD) {
44262         if(Ext.tip.QuickTipManager){
44263             Ext.tip.QuickTipManager.ddDisable();
44264         }
44265         if(this.dragCurrent){
44266             // the original browser mouseup wasn't handled (e.g. outside FF browser window)
44267             // so clean up first to avoid breaking the next drag
44268             this.handleMouseUp(e);
44269         }
44270         
44271         this.currentTarget = e.getTarget();
44272         this.dragCurrent = oDD;
44273
44274         var el = oDD.getEl();
44275
44276         // track start position
44277         this.startX = e.getPageX();
44278         this.startY = e.getPageY();
44279
44280         this.deltaX = this.startX - el.offsetLeft;
44281         this.deltaY = this.startY - el.offsetTop;
44282
44283         this.dragThreshMet = false;
44284
44285         this.clickTimeout = setTimeout(
44286                 function() {
44287                     var DDM = Ext.dd.DragDropManager;
44288                     DDM.startDrag(DDM.startX, DDM.startY);
44289                 },
44290                 this.clickTimeThresh );
44291     },
44292
44293     /**
44294      * Fired when either the drag pixel threshol or the mousedown hold
44295      * time threshold has been met.
44296      * @method startDrag
44297      * @param x {int} the X position of the original mousedown
44298      * @param y {int} the Y position of the original mousedown
44299      * @static
44300      */
44301     startDrag: function(x, y) {
44302         clearTimeout(this.clickTimeout);
44303         if (this.dragCurrent) {
44304             this.dragCurrent.b4StartDrag(x, y);
44305             this.dragCurrent.startDrag(x, y);
44306         }
44307         this.dragThreshMet = true;
44308     },
44309
44310     /**
44311      * Internal function to handle the mouseup event.  Will be invoked
44312      * from the context of the document.
44313      * @method handleMouseUp
44314      * @param {Event} e the event
44315      * @private
44316      * @static
44317      */
44318     handleMouseUp: function(e) {
44319
44320         if(Ext.tip.QuickTipManager){
44321             Ext.tip.QuickTipManager.ddEnable();
44322         }
44323         if (! this.dragCurrent) {
44324             return;
44325         }
44326
44327         clearTimeout(this.clickTimeout);
44328
44329         if (this.dragThreshMet) {
44330             this.fireEvents(e, true);
44331         } else {
44332         }
44333
44334         this.stopDrag(e);
44335
44336         this.stopEvent(e);
44337     },
44338
44339     /**
44340      * Utility to stop event propagation and event default, if these
44341      * features are turned on.
44342      * @method stopEvent
44343      * @param {Event} e the event as returned by this.getEvent()
44344      * @static
44345      */
44346     stopEvent: function(e){
44347         if(this.stopPropagation) {
44348             e.stopPropagation();
44349         }
44350
44351         if (this.preventDefault) {
44352             e.preventDefault();
44353         }
44354     },
44355
44356     /**
44357      * Internal function to clean up event handlers after the drag
44358      * operation is complete
44359      * @method stopDrag
44360      * @param {Event} e the event
44361      * @private
44362      * @static
44363      */
44364     stopDrag: function(e) {
44365         // Fire the drag end event for the item that was dragged
44366         if (this.dragCurrent) {
44367             if (this.dragThreshMet) {
44368                 this.dragCurrent.b4EndDrag(e);
44369                 this.dragCurrent.endDrag(e);
44370             }
44371
44372             this.dragCurrent.onMouseUp(e);
44373         }
44374
44375         this.dragCurrent = null;
44376         this.dragOvers = {};
44377     },
44378
44379     /**
44380      * Internal function to handle the mousemove event.  Will be invoked
44381      * from the context of the html element.
44382      *
44383      * @TODO figure out what we can do about mouse events lost when the
44384      * user drags objects beyond the window boundary.  Currently we can
44385      * detect this in internet explorer by verifying that the mouse is
44386      * down during the mousemove event.  Firefox doesn't give us the
44387      * button state on the mousemove event.
44388      * @method handleMouseMove
44389      * @param {Event} e the event
44390      * @private
44391      * @static
44392      */
44393     handleMouseMove: function(e) {
44394         if (! this.dragCurrent) {
44395             return true;
44396         }
44397         // var button = e.which || e.button;
44398
44399         // check for IE mouseup outside of page boundary
44400         if (Ext.isIE && (e.button !== 0 && e.button !== 1 && e.button !== 2)) {
44401             this.stopEvent(e);
44402             return this.handleMouseUp(e);
44403         }
44404
44405         if (!this.dragThreshMet) {
44406             var diffX = Math.abs(this.startX - e.getPageX());
44407             var diffY = Math.abs(this.startY - e.getPageY());
44408             if (diffX > this.clickPixelThresh ||
44409                         diffY > this.clickPixelThresh) {
44410                 this.startDrag(this.startX, this.startY);
44411             }
44412         }
44413
44414         if (this.dragThreshMet) {
44415             this.dragCurrent.b4Drag(e);
44416             this.dragCurrent.onDrag(e);
44417             if(!this.dragCurrent.moveOnly){
44418                 this.fireEvents(e, false);
44419             }
44420         }
44421
44422         this.stopEvent(e);
44423
44424         return true;
44425     },
44426
44427     /**
44428      * Iterates over all of the DragDrop elements to find ones we are
44429      * hovering over or dropping on
44430      * @method fireEvents
44431      * @param {Event} e the event
44432      * @param {boolean} isDrop is this a drop op or a mouseover op?
44433      * @private
44434      * @static
44435      */
44436     fireEvents: function(e, isDrop) {
44437         var dc = this.dragCurrent;
44438
44439         // If the user did the mouse up outside of the window, we could
44440         // get here even though we have ended the drag.
44441         if (!dc || dc.isLocked()) {
44442             return;
44443         }
44444
44445         var pt = e.getPoint();
44446
44447         // cache the previous dragOver array
44448         var oldOvers = [];
44449
44450         var outEvts   = [];
44451         var overEvts  = [];
44452         var dropEvts  = [];
44453         var enterEvts = [];
44454
44455         // Check to see if the object(s) we were hovering over is no longer
44456         // being hovered over so we can fire the onDragOut event
44457         for (var i in this.dragOvers) {
44458
44459             var ddo = this.dragOvers[i];
44460
44461             if (! this.isTypeOfDD(ddo)) {
44462                 continue;
44463             }
44464
44465             if (! this.isOverTarget(pt, ddo, this.mode)) {
44466                 outEvts.push( ddo );
44467             }
44468
44469             oldOvers[i] = true;
44470             delete this.dragOvers[i];
44471         }
44472
44473         for (var sGroup in dc.groups) {
44474
44475             if ("string" != typeof sGroup) {
44476                 continue;
44477             }
44478
44479             for (i in this.ids[sGroup]) {
44480                 var oDD = this.ids[sGroup][i];
44481                 if (! this.isTypeOfDD(oDD)) {
44482                     continue;
44483                 }
44484
44485                 if (oDD.isTarget && !oDD.isLocked() && ((oDD != dc) || (dc.ignoreSelf === false))) {
44486                     if (this.isOverTarget(pt, oDD, this.mode)) {
44487                         // look for drop interactions
44488                         if (isDrop) {
44489                             dropEvts.push( oDD );
44490                         // look for drag enter and drag over interactions
44491                         } else {
44492
44493                             // initial drag over: dragEnter fires
44494                             if (!oldOvers[oDD.id]) {
44495                                 enterEvts.push( oDD );
44496                             // subsequent drag overs: dragOver fires
44497                             } else {
44498                                 overEvts.push( oDD );
44499                             }
44500
44501                             this.dragOvers[oDD.id] = oDD;
44502                         }
44503                     }
44504                 }
44505             }
44506         }
44507
44508         if (this.mode) {
44509             if (outEvts.length) {
44510                 dc.b4DragOut(e, outEvts);
44511                 dc.onDragOut(e, outEvts);
44512             }
44513
44514             if (enterEvts.length) {
44515                 dc.onDragEnter(e, enterEvts);
44516             }
44517
44518             if (overEvts.length) {
44519                 dc.b4DragOver(e, overEvts);
44520                 dc.onDragOver(e, overEvts);
44521             }
44522
44523             if (dropEvts.length) {
44524                 dc.b4DragDrop(e, dropEvts);
44525                 dc.onDragDrop(e, dropEvts);
44526             }
44527
44528         } else {
44529             // fire dragout events
44530             var len = 0;
44531             for (i=0, len=outEvts.length; i<len; ++i) {
44532                 dc.b4DragOut(e, outEvts[i].id);
44533                 dc.onDragOut(e, outEvts[i].id);
44534             }
44535
44536             // fire enter events
44537             for (i=0,len=enterEvts.length; i<len; ++i) {
44538                 // dc.b4DragEnter(e, oDD.id);
44539                 dc.onDragEnter(e, enterEvts[i].id);
44540             }
44541
44542             // fire over events
44543             for (i=0,len=overEvts.length; i<len; ++i) {
44544                 dc.b4DragOver(e, overEvts[i].id);
44545                 dc.onDragOver(e, overEvts[i].id);
44546             }
44547
44548             // fire drop events
44549             for (i=0, len=dropEvts.length; i<len; ++i) {
44550                 dc.b4DragDrop(e, dropEvts[i].id);
44551                 dc.onDragDrop(e, dropEvts[i].id);
44552             }
44553
44554         }
44555
44556         // notify about a drop that did not find a target
44557         if (isDrop && !dropEvts.length) {
44558             dc.onInvalidDrop(e);
44559         }
44560
44561     },
44562
44563     /**
44564      * Helper function for getting the best match from the list of drag
44565      * and drop objects returned by the drag and drop events when we are
44566      * in INTERSECT mode.  It returns either the first object that the
44567      * cursor is over, or the object that has the greatest overlap with
44568      * the dragged element.
44569      * @method getBestMatch
44570      * @param  {DragDrop[]} dds The array of drag and drop objects
44571      * targeted
44572      * @return {DragDrop}       The best single match
44573      * @static
44574      */
44575     getBestMatch: function(dds) {
44576         var winner = null;
44577         // Return null if the input is not what we expect
44578         //if (!dds || !dds.length || dds.length == 0) {
44579            // winner = null;
44580         // If there is only one item, it wins
44581         //} else if (dds.length == 1) {
44582
44583         var len = dds.length;
44584
44585         if (len == 1) {
44586             winner = dds[0];
44587         } else {
44588             // Loop through the targeted items
44589             for (var i=0; i<len; ++i) {
44590                 var dd = dds[i];
44591                 // If the cursor is over the object, it wins.  If the
44592                 // cursor is over multiple matches, the first one we come
44593                 // to wins.
44594                 if (dd.cursorIsOver) {
44595                     winner = dd;
44596                     break;
44597                 // Otherwise the object with the most overlap wins
44598                 } else {
44599                     if (!winner ||
44600                         winner.overlap.getArea() < dd.overlap.getArea()) {
44601                         winner = dd;
44602                     }
44603                 }
44604             }
44605         }
44606
44607         return winner;
44608     },
44609
44610     /**
44611      * Refreshes the cache of the top-left and bottom-right points of the
44612      * drag and drop objects in the specified group(s).  This is in the
44613      * format that is stored in the drag and drop instance, so typical
44614      * usage is:
44615      * <code>
44616      * Ext.dd.DragDropManager.refreshCache(ddinstance.groups);
44617      * </code>
44618      * Alternatively:
44619      * <code>
44620      * Ext.dd.DragDropManager.refreshCache({group1:true, group2:true});
44621      * </code>
44622      * @TODO this really should be an indexed array.  Alternatively this
44623      * method could accept both.
44624      * @method refreshCache
44625      * @param {Object} groups an associative array of groups to refresh
44626      * @static
44627      */
44628     refreshCache: function(groups) {
44629         for (var sGroup in groups) {
44630             if ("string" != typeof sGroup) {
44631                 continue;
44632             }
44633             for (var i in this.ids[sGroup]) {
44634                 var oDD = this.ids[sGroup][i];
44635
44636                 if (this.isTypeOfDD(oDD)) {
44637                 // if (this.isTypeOfDD(oDD) && oDD.isTarget) {
44638                     var loc = this.getLocation(oDD);
44639                     if (loc) {
44640                         this.locationCache[oDD.id] = loc;
44641                     } else {
44642                         delete this.locationCache[oDD.id];
44643                         // this will unregister the drag and drop object if
44644                         // the element is not in a usable state
44645                         // oDD.unreg();
44646                     }
44647                 }
44648             }
44649         }
44650     },
44651
44652     /**
44653      * This checks to make sure an element exists and is in the DOM.  The
44654      * main purpose is to handle cases where innerHTML is used to remove
44655      * drag and drop objects from the DOM.  IE provides an 'unspecified
44656      * error' when trying to access the offsetParent of such an element
44657      * @method verifyEl
44658      * @param {HTMLElement} el the element to check
44659      * @return {boolean} true if the element looks usable
44660      * @static
44661      */
44662     verifyEl: function(el) {
44663         if (el) {
44664             var parent;
44665             if(Ext.isIE){
44666                 try{
44667                     parent = el.offsetParent;
44668                 }catch(e){}
44669             }else{
44670                 parent = el.offsetParent;
44671             }
44672             if (parent) {
44673                 return true;
44674             }
44675         }
44676
44677         return false;
44678     },
44679
44680     /**
44681      * Returns a Region object containing the drag and drop element's position
44682      * and size, including the padding configured for it
44683      * @method getLocation
44684      * @param {DragDrop} oDD the drag and drop object to get the
44685      *                       location for
44686      * @return {Ext.util.Region} a Region object representing the total area
44687      *                             the element occupies, including any padding
44688      *                             the instance is configured for.
44689      * @static
44690      */
44691     getLocation: function(oDD) {
44692         if (! this.isTypeOfDD(oDD)) {
44693             return null;
44694         }
44695
44696         //delegate getLocation method to the
44697         //drag and drop target.
44698         if (oDD.getRegion) {
44699             return oDD.getRegion();
44700         }
44701
44702         var el = oDD.getEl(), pos, x1, x2, y1, y2, t, r, b, l;
44703
44704         try {
44705             pos= Ext.core.Element.getXY(el);
44706         } catch (e) { }
44707
44708         if (!pos) {
44709             return null;
44710         }
44711
44712         x1 = pos[0];
44713         x2 = x1 + el.offsetWidth;
44714         y1 = pos[1];
44715         y2 = y1 + el.offsetHeight;
44716
44717         t = y1 - oDD.padding[0];
44718         r = x2 + oDD.padding[1];
44719         b = y2 + oDD.padding[2];
44720         l = x1 - oDD.padding[3];
44721
44722         return Ext.create('Ext.util.Region', t, r, b, l);
44723     },
44724
44725     /**
44726      * Checks the cursor location to see if it over the target
44727      * @method isOverTarget
44728      * @param {Ext.util.Point} pt The point to evaluate
44729      * @param {DragDrop} oTarget the DragDrop object we are inspecting
44730      * @return {boolean} true if the mouse is over the target
44731      * @private
44732      * @static
44733      */
44734     isOverTarget: function(pt, oTarget, intersect) {
44735         // use cache if available
44736         var loc = this.locationCache[oTarget.id];
44737         if (!loc || !this.useCache) {
44738             loc = this.getLocation(oTarget);
44739             this.locationCache[oTarget.id] = loc;
44740
44741         }
44742
44743         if (!loc) {
44744             return false;
44745         }
44746
44747         oTarget.cursorIsOver = loc.contains( pt );
44748
44749         // DragDrop is using this as a sanity check for the initial mousedown
44750         // in this case we are done.  In POINT mode, if the drag obj has no
44751         // contraints, we are also done. Otherwise we need to evaluate the
44752         // location of the target as related to the actual location of the
44753         // dragged element.
44754         var dc = this.dragCurrent;
44755         if (!dc || !dc.getTargetCoord ||
44756                 (!intersect && !dc.constrainX && !dc.constrainY)) {
44757             return oTarget.cursorIsOver;
44758         }
44759
44760         oTarget.overlap = null;
44761
44762         // Get the current location of the drag element, this is the
44763         // location of the mouse event less the delta that represents
44764         // where the original mousedown happened on the element.  We
44765         // need to consider constraints and ticks as well.
44766         var pos = dc.getTargetCoord(pt.x, pt.y);
44767
44768         var el = dc.getDragEl();
44769         var curRegion = Ext.create('Ext.util.Region', pos.y,
44770                                                pos.x + el.offsetWidth,
44771                                                pos.y + el.offsetHeight,
44772                                                pos.x );
44773
44774         var overlap = curRegion.intersect(loc);
44775
44776         if (overlap) {
44777             oTarget.overlap = overlap;
44778             return (intersect) ? true : oTarget.cursorIsOver;
44779         } else {
44780             return false;
44781         }
44782     },
44783
44784     /**
44785      * unload event handler
44786      * @method _onUnload
44787      * @private
44788      * @static
44789      */
44790     _onUnload: function(e, me) {
44791         Ext.dd.DragDropManager.unregAll();
44792     },
44793
44794     /**
44795      * Cleans up the drag and drop events and objects.
44796      * @method unregAll
44797      * @private
44798      * @static
44799      */
44800     unregAll: function() {
44801
44802         if (this.dragCurrent) {
44803             this.stopDrag();
44804             this.dragCurrent = null;
44805         }
44806
44807         this._execOnAll("unreg", []);
44808
44809         for (var i in this.elementCache) {
44810             delete this.elementCache[i];
44811         }
44812
44813         this.elementCache = {};
44814         this.ids = {};
44815     },
44816
44817     /**
44818      * A cache of DOM elements
44819      * @property elementCache
44820      * @private
44821      * @static
44822      */
44823     elementCache: {},
44824
44825     /**
44826      * Get the wrapper for the DOM element specified
44827      * @method getElWrapper
44828      * @param {String} id the id of the element to get
44829      * @return {Ext.dd.DDM.ElementWrapper} the wrapped element
44830      * @private
44831      * @deprecated This wrapper isn't that useful
44832      * @static
44833      */
44834     getElWrapper: function(id) {
44835         var oWrapper = this.elementCache[id];
44836         if (!oWrapper || !oWrapper.el) {
44837             oWrapper = this.elementCache[id] =
44838                 new this.ElementWrapper(Ext.getDom(id));
44839         }
44840         return oWrapper;
44841     },
44842
44843     /**
44844      * Returns the actual DOM element
44845      * @method getElement
44846      * @param {String} id the id of the elment to get
44847      * @return {Object} The element
44848      * @deprecated use Ext.lib.Ext.getDom instead
44849      * @static
44850      */
44851     getElement: function(id) {
44852         return Ext.getDom(id);
44853     },
44854
44855     /**
44856      * Returns the style property for the DOM element (i.e.,
44857      * document.getElById(id).style)
44858      * @method getCss
44859      * @param {String} id the id of the elment to get
44860      * @return {Object} The style property of the element
44861      * @static
44862      */
44863     getCss: function(id) {
44864         var el = Ext.getDom(id);
44865         return (el) ? el.style : null;
44866     },
44867
44868     /**
44869      * Inner class for cached elements
44870      * @class Ext.dd.DragDropManager.ElementWrapper
44871      * @for DragDropManager
44872      * @private
44873      * @deprecated
44874      */
44875     ElementWrapper: function(el) {
44876             /**
44877              * The element
44878              * @property el
44879              */
44880             this.el = el || null;
44881             /**
44882              * The element id
44883              * @property id
44884              */
44885             this.id = this.el && el.id;
44886             /**
44887              * A reference to the style property
44888              * @property css
44889              */
44890             this.css = this.el && el.style;
44891         },
44892
44893     /**
44894      * Returns the X position of an html element
44895      * @method getPosX
44896      * @param el the element for which to get the position
44897      * @return {int} the X coordinate
44898      * @for DragDropManager
44899      * @static
44900      */
44901     getPosX: function(el) {
44902         return Ext.core.Element.getX(el);
44903     },
44904
44905     /**
44906      * Returns the Y position of an html element
44907      * @method getPosY
44908      * @param el the element for which to get the position
44909      * @return {int} the Y coordinate
44910      * @static
44911      */
44912     getPosY: function(el) {
44913         return Ext.core.Element.getY(el);
44914     },
44915
44916     /**
44917      * Swap two nodes.  In IE, we use the native method, for others we
44918      * emulate the IE behavior
44919      * @method swapNode
44920      * @param n1 the first node to swap
44921      * @param n2 the other node to swap
44922      * @static
44923      */
44924     swapNode: function(n1, n2) {
44925         if (n1.swapNode) {
44926             n1.swapNode(n2);
44927         } else {
44928             var p = n2.parentNode;
44929             var s = n2.nextSibling;
44930
44931             if (s == n1) {
44932                 p.insertBefore(n1, n2);
44933             } else if (n2 == n1.nextSibling) {
44934                 p.insertBefore(n2, n1);
44935             } else {
44936                 n1.parentNode.replaceChild(n2, n1);
44937                 p.insertBefore(n1, s);
44938             }
44939         }
44940     },
44941
44942     /**
44943      * Returns the current scroll position
44944      * @method getScroll
44945      * @private
44946      * @static
44947      */
44948     getScroll: function () {
44949         var doc   = window.document,
44950             docEl = doc.documentElement,
44951             body  = doc.body,
44952             top   = 0,
44953             left  = 0;
44954             
44955         if (Ext.isGecko4) {
44956             top  = window.scrollYOffset;
44957             left = window.scrollXOffset;
44958         } else {
44959             if (docEl && (docEl.scrollTop || docEl.scrollLeft)) {
44960                 top  = docEl.scrollTop;
44961                 left = docEl.scrollLeft;
44962             } else if (body) {
44963                 top  = body.scrollTop;
44964                 left = body.scrollLeft;
44965             } 
44966         }
44967         return {
44968             top: top,
44969             left: left
44970         };
44971     },
44972
44973     /**
44974      * Returns the specified element style property
44975      * @method getStyle
44976      * @param {HTMLElement} el          the element
44977      * @param {string}      styleProp   the style property
44978      * @return {string} The value of the style property
44979      * @static
44980      */
44981     getStyle: function(el, styleProp) {
44982         return Ext.fly(el).getStyle(styleProp);
44983     },
44984
44985     /**
44986      * Gets the scrollTop
44987      * @method getScrollTop
44988      * @return {int} the document's scrollTop
44989      * @static
44990      */
44991     getScrollTop: function () {
44992         return this.getScroll().top;
44993     },
44994
44995     /**
44996      * Gets the scrollLeft
44997      * @method getScrollLeft
44998      * @return {int} the document's scrollTop
44999      * @static
45000      */
45001     getScrollLeft: function () {
45002         return this.getScroll().left;
45003     },
45004
45005     /**
45006      * Sets the x/y position of an element to the location of the
45007      * target element.
45008      * @method moveToEl
45009      * @param {HTMLElement} moveEl      The element to move
45010      * @param {HTMLElement} targetEl    The position reference element
45011      * @static
45012      */
45013     moveToEl: function (moveEl, targetEl) {
45014         var aCoord = Ext.core.Element.getXY(targetEl);
45015         Ext.core.Element.setXY(moveEl, aCoord);
45016     },
45017
45018     /**
45019      * Numeric array sort function
45020      * @method numericSort
45021      * @static
45022      */
45023     numericSort: function(a, b) {
45024         return (a - b);
45025     },
45026
45027     /**
45028      * Internal counter
45029      * @property _timeoutCount
45030      * @private
45031      * @static
45032      */
45033     _timeoutCount: 0,
45034
45035     /**
45036      * Trying to make the load order less important.  Without this we get
45037      * an error if this file is loaded before the Event Utility.
45038      * @method _addListeners
45039      * @private
45040      * @static
45041      */
45042     _addListeners: function() {
45043         if ( document ) {
45044             this._onLoad();
45045         } else {
45046             if (this._timeoutCount > 2000) {
45047             } else {
45048                 setTimeout(this._addListeners, 10);
45049                 if (document && document.body) {
45050                     this._timeoutCount += 1;
45051                 }
45052             }
45053         }
45054     },
45055
45056     /**
45057      * Recursively searches the immediate parent and all child nodes for
45058      * the handle element in order to determine wheter or not it was
45059      * clicked.
45060      * @method handleWasClicked
45061      * @param node the html element to inspect
45062      * @static
45063      */
45064     handleWasClicked: function(node, id) {
45065         if (this.isHandle(id, node.id)) {
45066             return true;
45067         } else {
45068             // check to see if this is a text node child of the one we want
45069             var p = node.parentNode;
45070
45071             while (p) {
45072                 if (this.isHandle(id, p.id)) {
45073                     return true;
45074                 } else {
45075                     p = p.parentNode;
45076                 }
45077             }
45078         }
45079
45080         return false;
45081     }
45082 }, function() {
45083     this._addListeners();
45084 });
45085
45086 /**
45087  * @class Ext.layout.container.Box
45088  * @extends Ext.layout.container.Container
45089  * <p>Base Class for HBoxLayout and VBoxLayout Classes. Generally it should not need to be used directly.</p>
45090  */
45091
45092 Ext.define('Ext.layout.container.Box', {
45093
45094     /* Begin Definitions */
45095
45096     alias: ['layout.box'],
45097     extend: 'Ext.layout.container.Container',
45098     alternateClassName: 'Ext.layout.BoxLayout',
45099     
45100     requires: [
45101         'Ext.layout.container.boxOverflow.None',
45102         'Ext.layout.container.boxOverflow.Menu',
45103         'Ext.layout.container.boxOverflow.Scroller',
45104         'Ext.util.Format',
45105         'Ext.dd.DragDropManager'
45106     ],
45107
45108     /* End Definitions */
45109
45110     /**
45111      * @cfg {Mixed} animate
45112      * <p>If truthy, child Component are <i>animated</i> into position whenever the Container
45113      * is layed out. If this option is numeric, it is used as the animation duration in milliseconds.</p>
45114      * <p>May be set as a property at any time.</p>
45115      */
45116
45117     /**
45118      * @cfg {Object} defaultMargins
45119      * <p>If the individual contained items do not have a <tt>margins</tt>
45120      * property specified or margin specified via CSS, the default margins from this property will be
45121      * applied to each item.</p>
45122      * <br><p>This property may be specified as an object containing margins
45123      * to apply in the format:</p><pre><code>
45124 {
45125     top: (top margin),
45126     right: (right margin),
45127     bottom: (bottom margin),
45128     left: (left margin)
45129 }</code></pre>
45130      * <p>This property may also be specified as a string containing
45131      * space-separated, numeric margin values. The order of the sides associated
45132      * with each value matches the way CSS processes margin values:</p>
45133      * <div class="mdetail-params"><ul>
45134      * <li>If there is only one value, it applies to all sides.</li>
45135      * <li>If there are two values, the top and bottom borders are set to the
45136      * first value and the right and left are set to the second.</li>
45137      * <li>If there are three values, the top is set to the first value, the left
45138      * and right are set to the second, and the bottom is set to the third.</li>
45139      * <li>If there are four values, they apply to the top, right, bottom, and
45140      * left, respectively.</li>
45141      * </ul></div>
45142      * <p>Defaults to:</p><pre><code>
45143      * {top:0, right:0, bottom:0, left:0}
45144      * </code></pre>
45145      */
45146     defaultMargins: {
45147         top: 0,
45148         right: 0,
45149         bottom: 0,
45150         left: 0
45151     },
45152
45153     /**
45154      * @cfg {String} padding
45155      * <p>Sets the padding to be applied to all child items managed by this layout.</p>
45156      * <p>This property must be specified as a string containing
45157      * space-separated, numeric padding values. The order of the sides associated
45158      * with each value matches the way CSS processes padding values:</p>
45159      * <div class="mdetail-params"><ul>
45160      * <li>If there is only one value, it applies to all sides.</li>
45161      * <li>If there are two values, the top and bottom borders are set to the
45162      * first value and the right and left are set to the second.</li>
45163      * <li>If there are three values, the top is set to the first value, the left
45164      * and right are set to the second, and the bottom is set to the third.</li>
45165      * <li>If there are four values, they apply to the top, right, bottom, and
45166      * left, respectively.</li>
45167      * </ul></div>
45168      * <p>Defaults to: <code>"0"</code></p>
45169      */
45170     padding: '0',
45171     // documented in subclasses
45172     pack: 'start',
45173
45174     /**
45175      * @cfg {String} pack
45176      * Controls how the child items of the container are packed together. Acceptable configuration values
45177      * for this property are:
45178      * <div class="mdetail-params"><ul>
45179      * <li><b><tt>start</tt></b> : <b>Default</b><div class="sub-desc">child items are packed together at
45180      * <b>left</b> side of container</div></li>
45181      * <li><b><tt>center</tt></b> : <div class="sub-desc">child items are packed together at
45182      * <b>mid-width</b> of container</div></li>
45183      * <li><b><tt>end</tt></b> : <div class="sub-desc">child items are packed together at <b>right</b>
45184      * side of container</div></li>
45185      * </ul></div>
45186      */
45187     /**
45188      * @cfg {Number} flex
45189      * This configuration option is to be applied to <b>child <tt>items</tt></b> of the container managed
45190      * by this layout. Each child item with a <tt>flex</tt> property will be flexed <b>horizontally</b>
45191      * according to each item's <b>relative</b> <tt>flex</tt> value compared to the sum of all items with
45192      * a <tt>flex</tt> value specified.  Any child items that have either a <tt>flex = 0</tt> or
45193      * <tt>flex = undefined</tt> will not be 'flexed' (the initial size will not be changed).
45194      */
45195
45196     type: 'box',
45197     scrollOffset: 0,
45198     itemCls: Ext.baseCSSPrefix + 'box-item',
45199     targetCls: Ext.baseCSSPrefix + 'box-layout-ct',
45200     innerCls: Ext.baseCSSPrefix + 'box-inner',
45201
45202     bindToOwnerCtContainer: true,
45203
45204     fixedLayout: false,
45205     
45206     // availableSpaceOffset is used to adjust the availableWidth, typically used
45207     // to reserve space for a scrollbar
45208     availableSpaceOffset: 0,
45209     
45210     // whether or not to reserve the availableSpaceOffset in layout calculations
45211     reserveOffset: true,
45212     
45213     /**
45214      * @cfg {Boolean} clearInnerCtOnLayout
45215      */
45216     clearInnerCtOnLayout: false,
45217
45218     flexSortFn: function (a, b) {
45219         var maxParallelPrefix = 'max' + this.parallelPrefixCap,
45220             infiniteValue = Infinity;
45221         a = a.component[maxParallelPrefix] || infiniteValue;
45222         b = b.component[maxParallelPrefix] || infiniteValue;
45223         // IE 6/7 Don't like Infinity - Infinity...
45224         if (!isFinite(a) && !isFinite(b)) {
45225             return false;
45226         }
45227         return a - b;
45228     },
45229
45230     // Sort into *descending* order.
45231     minSizeSortFn: function(a, b) {
45232         return b.available - a.available;
45233     },
45234
45235     constructor: function(config) {
45236         var me = this;
45237
45238         me.callParent(arguments);
45239
45240         // The sort function needs access to properties in this, so must be bound.
45241         me.flexSortFn = Ext.Function.bind(me.flexSortFn, me);
45242
45243         me.initOverflowHandler();
45244     },
45245
45246     /**
45247      * @private
45248      * Returns the current size and positioning of the passed child item.
45249      * @param {Component} child The child Component to calculate the box for
45250      * @return {Object} Object containing box measurements for the child. Properties are left,top,width,height.
45251      */
45252     getChildBox: function(child) {
45253         child = child.el || this.owner.getComponent(child).el;
45254         return {
45255             left: child.getLeft(true),
45256             top: child.getTop(true),
45257             width: child.getWidth(),
45258             height: child.getHeight()
45259         };
45260     },
45261
45262     /**
45263      * @private
45264      * Calculates the size and positioning of the passed child item.
45265      * @param {Component} child The child Component to calculate the box for
45266      * @return {Object} Object containing box measurements for the child. Properties are left,top,width,height.
45267      */
45268     calculateChildBox: function(child) {
45269         var me = this,
45270             boxes = me.calculateChildBoxes(me.getVisibleItems(), me.getLayoutTargetSize()).boxes,
45271             ln = boxes.length,
45272             i = 0;
45273
45274         child = me.owner.getComponent(child);
45275         for (; i < ln; i++) {
45276             if (boxes[i].component === child) {
45277                 return boxes[i];
45278             }
45279         }
45280     },
45281
45282     /**
45283      * @private
45284      * Calculates the size and positioning of each item in the box. This iterates over all of the rendered,
45285      * visible items and returns a height, width, top and left for each, as well as a reference to each. Also
45286      * returns meta data such as maxSize which are useful when resizing layout wrappers such as this.innerCt.
45287      * @param {Array} visibleItems The array of all rendered, visible items to be calculated for
45288      * @param {Object} targetSize Object containing target size and height
45289      * @return {Object} Object containing box measurements for each child, plus meta data
45290      */
45291     calculateChildBoxes: function(visibleItems, targetSize) {
45292         var me = this,
45293             math = Math,
45294             mmax = math.max,
45295             infiniteValue = Infinity,
45296             undefinedValue,
45297
45298             parallelPrefix = me.parallelPrefix,
45299             parallelPrefixCap = me.parallelPrefixCap,
45300             perpendicularPrefix = me.perpendicularPrefix,
45301             perpendicularPrefixCap = me.perpendicularPrefixCap,
45302             parallelMinString = 'min' + parallelPrefixCap,
45303             perpendicularMinString = 'min' + perpendicularPrefixCap,
45304             perpendicularMaxString = 'max' + perpendicularPrefixCap,
45305
45306             parallelSize = targetSize[parallelPrefix] - me.scrollOffset,
45307             perpendicularSize = targetSize[perpendicularPrefix],
45308             padding = me.padding,
45309             parallelOffset = padding[me.parallelBefore],
45310             paddingParallel = parallelOffset + padding[me.parallelAfter],
45311             perpendicularOffset = padding[me.perpendicularLeftTop],
45312             paddingPerpendicular =  perpendicularOffset + padding[me.perpendicularRightBottom],
45313             availPerpendicularSize = mmax(0, perpendicularSize - paddingPerpendicular),
45314
45315             isStart = me.pack == 'start',
45316             isCenter = me.pack == 'center',
45317             isEnd = me.pack == 'end',
45318
45319             constrain = Ext.Number.constrain,
45320             visibleCount = visibleItems.length,
45321             nonFlexSize = 0,
45322             totalFlex = 0,
45323             desiredSize = 0,
45324             minimumSize = 0,
45325             maxSize = 0,
45326             boxes = [],
45327             minSizes = [],
45328             calculatedWidth,
45329
45330             i, child, childParallel, childPerpendicular, childMargins, childSize, minParallel, tmpObj, shortfall, 
45331             tooNarrow, availableSpace, minSize, item, length, itemIndex, box, oldSize, newSize, reduction, diff, 
45332             flexedBoxes, remainingSpace, remainingFlex, flexedSize, parallelMargins, calcs, offset, 
45333             perpendicularMargins, stretchSize;
45334
45335         //gather the total flex of all flexed items and the width taken up by fixed width items
45336         for (i = 0; i < visibleCount; i++) {
45337             child = visibleItems[i];
45338             childPerpendicular = child[perpendicularPrefix];
45339             me.layoutItem(child);
45340             childMargins = child.margins;
45341             parallelMargins = childMargins[me.parallelBefore] + childMargins[me.parallelAfter];
45342
45343             // Create the box description object for this child item.
45344             tmpObj = {
45345                 component: child,
45346                 margins: childMargins
45347             };
45348
45349             // flex and not 'auto' width
45350             if (child.flex) {
45351                 totalFlex += child.flex;
45352                 childParallel = undefinedValue;
45353             }
45354             // Not flexed or 'auto' width or undefined width
45355             else {
45356                 if (!(child[parallelPrefix] && childPerpendicular)) {
45357                     childSize = child.getSize();
45358                 }
45359                 childParallel = child[parallelPrefix] || childSize[parallelPrefix];
45360                 childPerpendicular = childPerpendicular || childSize[perpendicularPrefix];
45361             }
45362
45363             nonFlexSize += parallelMargins + (childParallel || 0);
45364             desiredSize += parallelMargins + (child.flex ? child[parallelMinString] || 0 : childParallel);
45365             minimumSize += parallelMargins + (child[parallelMinString] || childParallel || 0);
45366
45367             // Max height for align - force layout of non-laid out subcontainers without a numeric height
45368             if (typeof childPerpendicular != 'number') {
45369                 // Clear any static sizing and revert to flow so we can get a proper measurement
45370                 // child['set' + perpendicularPrefixCap](null);
45371                 childPerpendicular = child['get' + perpendicularPrefixCap]();
45372             }
45373
45374             // Track the maximum perpendicular size for use by the stretch and stretchmax align config values.
45375             maxSize = mmax(maxSize, childPerpendicular + childMargins[me.perpendicularLeftTop] + childMargins[me.perpendicularRightBottom]);
45376
45377             tmpObj[parallelPrefix] = childParallel || undefinedValue;
45378             tmpObj[perpendicularPrefix] = childPerpendicular || undefinedValue;
45379             boxes.push(tmpObj);
45380         }
45381         shortfall = desiredSize - parallelSize;
45382         tooNarrow = minimumSize > parallelSize;
45383
45384         //the space available to the flexed items
45385         availableSpace = mmax(0, parallelSize - nonFlexSize - paddingParallel - (me.reserveOffset ? me.availableSpaceOffset : 0));
45386
45387         if (tooNarrow) {
45388             for (i = 0; i < visibleCount; i++) {
45389                 box = boxes[i];
45390                 minSize = visibleItems[i][parallelMinString] || visibleItems[i][parallelPrefix] || box[parallelPrefix];
45391                 box.dirtySize = box.dirtySize || box[parallelPrefix] != minSize;
45392                 box[parallelPrefix] = minSize;
45393             }
45394         }
45395         else {
45396             //all flexed items should be sized to their minimum size, other items should be shrunk down until
45397             //the shortfall has been accounted for
45398             if (shortfall > 0) {
45399                 /*
45400                  * When we have a shortfall but are not tooNarrow, we need to shrink the width of each non-flexed item.
45401                  * Flexed items are immediately reduced to their minWidth and anything already at minWidth is ignored.
45402                  * The remaining items are collected into the minWidths array, which is later used to distribute the shortfall.
45403                  */
45404                 for (i = 0; i < visibleCount; i++) {
45405                     item = visibleItems[i];
45406                     minSize = item[parallelMinString] || 0;
45407
45408                     //shrink each non-flex tab by an equal amount to make them all fit. Flexed items are all
45409                     //shrunk to their minSize because they're flexible and should be the first to lose size
45410                     if (item.flex) {
45411                         box = boxes[i];
45412                         box.dirtySize = box.dirtySize || box[parallelPrefix] != minSize;
45413                         box[parallelPrefix] = minSize;
45414                     }
45415                     else {
45416                         minSizes.push({
45417                             minSize: minSize,
45418                             available: boxes[i][parallelPrefix] - minSize,
45419                             index: i
45420                         });
45421                     }
45422                 }
45423
45424                 //sort by descending amount of width remaining before minWidth is reached
45425                 Ext.Array.sort(minSizes, me.minSizeSortFn);
45426
45427                 /*
45428                  * Distribute the shortfall (difference between total desired size of all items and actual size available)
45429                  * between the non-flexed items. We try to distribute the shortfall evenly, but apply it to items with the
45430                  * smallest difference between their size and minSize first, so that if reducing the size by the average
45431                  * amount would make that item less than its minSize, we carry the remainder over to the next item.
45432                  */
45433                 for (i = 0, length = minSizes.length; i < length; i++) {
45434                     itemIndex = minSizes[i].index;
45435
45436                     if (itemIndex == undefinedValue) {
45437                         continue;
45438                     }
45439                     item = visibleItems[itemIndex];
45440                     minSize = minSizes[i].minSize;
45441
45442                     box = boxes[itemIndex];
45443                     oldSize = box[parallelPrefix];
45444                     newSize = mmax(minSize, oldSize - math.ceil(shortfall / (length - i)));
45445                     reduction = oldSize - newSize;
45446
45447                     box.dirtySize = box.dirtySize || box[parallelPrefix] != newSize;
45448                     box[parallelPrefix] = newSize;
45449                     shortfall -= reduction;
45450                 }
45451             }
45452             else {
45453                 remainingSpace = availableSpace;
45454                 remainingFlex = totalFlex;
45455                 flexedBoxes = [];
45456
45457                 // Create an array containing *just the flexed boxes* for allocation of remainingSpace
45458                 for (i = 0; i < visibleCount; i++) {
45459                     child = visibleItems[i];
45460                     if (isStart && child.flex) {
45461                         flexedBoxes.push(boxes[Ext.Array.indexOf(visibleItems, child)]);
45462                     }
45463                 }
45464                 // The flexed boxes need to be sorted in ascending order of maxSize to work properly
45465                 // so that unallocated space caused by maxWidth being less than flexed width
45466                 // can be reallocated to subsequent flexed boxes.
45467                 Ext.Array.sort(flexedBoxes, me.flexSortFn);
45468
45469                 // Calculate the size of each flexed item, and attempt to set it.
45470                 for (i = 0; i < flexedBoxes.length; i++) {
45471                     calcs = flexedBoxes[i];
45472                     child = calcs.component;
45473                     childMargins = calcs.margins;
45474
45475                     flexedSize = math.ceil((child.flex / remainingFlex) * remainingSpace);
45476
45477                     // Implement maxSize and minSize check
45478                     flexedSize = Math.max(child['min' + parallelPrefixCap] || 0, math.min(child['max' + parallelPrefixCap] || infiniteValue, flexedSize));
45479
45480                     // Remaining space has already had all parallel margins subtracted from it, so just subtract consumed size
45481                     remainingSpace -= flexedSize;
45482                     remainingFlex -= child.flex;
45483
45484                     calcs.dirtySize = calcs.dirtySize || calcs[parallelPrefix] != flexedSize;
45485                     calcs[parallelPrefix] = flexedSize;
45486                 }
45487             }
45488         }
45489
45490         if (isCenter) {
45491             parallelOffset += availableSpace / 2;
45492         }
45493         else if (isEnd) {
45494             parallelOffset += availableSpace;
45495         }
45496
45497         // Fix for left and right docked Components in a dock component layout. This is for docked Headers and docked Toolbars.
45498         // Older Microsoft browsers do not size a position:absolute element's width to match its content.
45499         // So in this case, in the updateInnerCtSize method we may need to adjust the size of the owning Container's element explicitly based upon
45500         // the discovered max width. So here we put a calculatedWidth property in the metadata to facilitate this.
45501         if (me.owner.dock && (Ext.isIE6 || Ext.isIE7 || Ext.isIEQuirks) && !me.owner.width && me.direction == 'vertical') {
45502
45503             calculatedWidth = maxSize + me.owner.el.getPadding('lr') + me.owner.el.getBorderWidth('lr');
45504             if (me.owner.frameSize) {
45505                 calculatedWidth += me.owner.frameSize.left + me.owner.frameSize.right;
45506             }
45507             // If the owning element is not sized, calculate the available width to center or stretch in based upon maxSize
45508             availPerpendicularSize = Math.min(availPerpendicularSize, targetSize.width = maxSize + padding.left + padding.right);
45509         }
45510
45511         //finally, calculate the left and top position of each item
45512         for (i = 0; i < visibleCount; i++) {
45513             child = visibleItems[i];
45514             calcs = boxes[i];
45515
45516             childMargins = calcs.margins;
45517
45518             perpendicularMargins = childMargins[me.perpendicularLeftTop] + childMargins[me.perpendicularRightBottom];
45519
45520             // Advance past the "before" margin
45521             parallelOffset += childMargins[me.parallelBefore];
45522
45523             calcs[me.parallelBefore] = parallelOffset;
45524             calcs[me.perpendicularLeftTop] = perpendicularOffset + childMargins[me.perpendicularLeftTop];
45525
45526             if (me.align == 'stretch') {
45527                 stretchSize = constrain(availPerpendicularSize - perpendicularMargins, child[perpendicularMinString] || 0, child[perpendicularMaxString] || infiniteValue);
45528                 calcs.dirtySize = calcs.dirtySize || calcs[perpendicularPrefix] != stretchSize;
45529                 calcs[perpendicularPrefix] = stretchSize;
45530             }
45531             else if (me.align == 'stretchmax') {
45532                 stretchSize = constrain(maxSize - perpendicularMargins, child[perpendicularMinString] || 0, child[perpendicularMaxString] || infiniteValue);
45533                 calcs.dirtySize = calcs.dirtySize || calcs[perpendicularPrefix] != stretchSize;
45534                 calcs[perpendicularPrefix] = stretchSize;
45535             }
45536             else if (me.align == me.alignCenteringString) {
45537                 // When calculating a centered position within the content box of the innerCt, the width of the borders must be subtracted from
45538                 // the size to yield the space available to center within.
45539                 // The updateInnerCtSize method explicitly adds the border widths to the set size of the innerCt.
45540                 diff = mmax(availPerpendicularSize, maxSize) - me.innerCt.getBorderWidth(me.perpendicularLT + me.perpendicularRB) - calcs[perpendicularPrefix];
45541                 if (diff > 0) {
45542                     calcs[me.perpendicularLeftTop] = perpendicularOffset + Math.round(diff / 2);
45543                 }
45544             }
45545
45546             // Advance past the box size and the "after" margin
45547             parallelOffset += (calcs[parallelPrefix] || 0) + childMargins[me.parallelAfter];
45548         }
45549
45550         return {
45551             boxes: boxes,
45552             meta : {
45553                 calculatedWidth: calculatedWidth,
45554                 maxSize: maxSize,
45555                 nonFlexSize: nonFlexSize,
45556                 desiredSize: desiredSize,
45557                 minimumSize: minimumSize,
45558                 shortfall: shortfall,
45559                 tooNarrow: tooNarrow
45560             }
45561         };
45562     },
45563
45564     /**
45565      * @private
45566      */
45567     initOverflowHandler: function() {
45568         var handler = this.overflowHandler;
45569
45570         if (typeof handler == 'string') {
45571             handler = {
45572                 type: handler
45573             };
45574         }
45575
45576         var handlerType = 'None';
45577         if (handler && handler.type != undefined) {
45578             handlerType = handler.type;
45579         }
45580
45581         var constructor = Ext.layout.container.boxOverflow[handlerType];
45582         if (constructor[this.type]) {
45583             constructor = constructor[this.type];
45584         }
45585
45586         this.overflowHandler = Ext.create('Ext.layout.container.boxOverflow.' + handlerType, this, handler);
45587     },
45588
45589     /**
45590      * @private
45591      * Runs the child box calculations and caches them in childBoxCache. Subclasses can used these cached values
45592      * when laying out
45593      */
45594     onLayout: function() {
45595         this.callParent();
45596         // Clear the innerCt size so it doesn't influence the child items.
45597         if (this.clearInnerCtOnLayout === true && this.adjustmentPass !== true) {
45598             this.innerCt.setSize(null, null);
45599         }
45600
45601         var me = this,
45602             targetSize = me.getLayoutTargetSize(),
45603             items = me.getVisibleItems(),
45604             calcs = me.calculateChildBoxes(items, targetSize),
45605             boxes = calcs.boxes,
45606             meta = calcs.meta,
45607             handler, method, results;
45608
45609         if (me.autoSize && calcs.meta.desiredSize) {
45610             targetSize[me.parallelPrefix] = calcs.meta.desiredSize;
45611         }
45612
45613         //invoke the overflow handler, if one is configured
45614         if (meta.shortfall > 0) {
45615             handler = me.overflowHandler;
45616             method = meta.tooNarrow ? 'handleOverflow': 'clearOverflow';
45617
45618             results = handler[method](calcs, targetSize);
45619
45620             if (results) {
45621                 if (results.targetSize) {
45622                     targetSize = results.targetSize;
45623                 }
45624
45625                 if (results.recalculate) {
45626                     items = me.getVisibleItems(owner);
45627                     calcs = me.calculateChildBoxes(items, targetSize);
45628                     boxes = calcs.boxes;
45629                 }
45630             }
45631         } else {
45632             me.overflowHandler.clearOverflow();
45633         }
45634
45635         /**
45636          * @private
45637          * @property layoutTargetLastSize
45638          * @type Object
45639          * Private cache of the last measured size of the layout target. This should never be used except by
45640          * BoxLayout subclasses during their onLayout run.
45641          */
45642         me.layoutTargetLastSize = targetSize;
45643
45644         /**
45645          * @private
45646          * @property childBoxCache
45647          * @type Array
45648          * Array of the last calculated height, width, top and left positions of each visible rendered component
45649          * within the Box layout.
45650          */
45651         me.childBoxCache = calcs;
45652
45653         me.updateInnerCtSize(targetSize, calcs);
45654         me.updateChildBoxes(boxes);
45655         me.handleTargetOverflow(targetSize);
45656     },
45657
45658     /**
45659      * Resizes and repositions each child component
45660      * @param {Array} boxes The box measurements
45661      */
45662     updateChildBoxes: function(boxes) {
45663         var me = this,
45664             i = 0,
45665             length = boxes.length,
45666             animQueue = [],
45667             dd = Ext.dd.DDM.getDDById(me.innerCt.id), // Any DD active on this layout's element (The BoxReorderer plugin does this.)
45668             oldBox, newBox, changed, comp, boxAnim, animCallback;
45669
45670         for (; i < length; i++) {
45671             newBox = boxes[i];
45672             comp = newBox.component;
45673
45674             // If a Component is being drag/dropped, skip positioning it.
45675             // Accomodate the BoxReorderer plugin: Its current dragEl must not be positioned by the layout
45676             if (dd && (dd.getDragEl() === comp.el.dom)) {
45677                 continue;
45678             }
45679
45680             changed = false;
45681
45682             oldBox = me.getChildBox(comp);
45683
45684             // If we are animating, we build up an array of Anim config objects, one for each
45685             // child Component which has any changed box properties. Those with unchanged
45686             // properties are not animated.
45687             if (me.animate) {
45688                 // Animate may be a config object containing callback.
45689                 animCallback = me.animate.callback || me.animate;
45690                 boxAnim = {
45691                     layoutAnimation: true,  // Component Target handler must use set*Calculated*Size
45692                     target: comp,
45693                     from: {},
45694                     to: {},
45695                     listeners: {}
45696                 };
45697                 // Only set from and to properties when there's a change.
45698                 // Perform as few Component setter methods as possible.
45699                 // Temporarily set the property values that we are not animating
45700                 // so that doComponentLayout does not auto-size them.
45701                 if (!isNaN(newBox.width) && (newBox.width != oldBox.width)) {
45702                     changed = true;
45703                     // boxAnim.from.width = oldBox.width;
45704                     boxAnim.to.width = newBox.width;
45705                 }
45706                 if (!isNaN(newBox.height) && (newBox.height != oldBox.height)) {
45707                     changed = true;
45708                     // boxAnim.from.height = oldBox.height;
45709                     boxAnim.to.height = newBox.height;
45710                 }
45711                 if (!isNaN(newBox.left) && (newBox.left != oldBox.left)) {
45712                     changed = true;
45713                     // boxAnim.from.left = oldBox.left;
45714                     boxAnim.to.left = newBox.left;
45715                 }
45716                 if (!isNaN(newBox.top) && (newBox.top != oldBox.top)) {
45717                     changed = true;
45718                     // boxAnim.from.top = oldBox.top;
45719                     boxAnim.to.top = newBox.top;
45720                 }
45721                 if (changed) {
45722                     animQueue.push(boxAnim);
45723                 }
45724             } else {
45725                 if (newBox.dirtySize) {
45726                     if (newBox.width !== oldBox.width || newBox.height !== oldBox.height) {
45727                         me.setItemSize(comp, newBox.width, newBox.height);
45728                     }
45729                 }
45730                 // Don't set positions to NaN
45731                 if (isNaN(newBox.left) || isNaN(newBox.top)) {
45732                     continue;
45733                 }
45734                 comp.setPosition(newBox.left, newBox.top);
45735             }
45736         }
45737
45738         // Kick off any queued animations
45739         length = animQueue.length;
45740         if (length) {
45741
45742             // A function which cleans up when a Component's animation is done.
45743             // The last one to finish calls the callback.
45744             var afterAnimate = function(anim) {
45745                 // When we've animated all changed boxes into position, clear our busy flag and call the callback.
45746                 length -= 1;
45747                 if (!length) {
45748                     me.layoutBusy = false;
45749                     if (Ext.isFunction(animCallback)) {
45750                         animCallback();
45751                     }
45752                 }
45753             };
45754
45755             var beforeAnimate = function() {
45756                 me.layoutBusy = true;
45757             };
45758
45759             // Start each box animation off
45760             for (i = 0, length = animQueue.length; i < length; i++) {
45761                 boxAnim = animQueue[i];
45762
45763                 // Clean up the Component after. Clean up the *layout* after the last animation finishes
45764                 boxAnim.listeners.afteranimate = afterAnimate;
45765
45766                 // The layout is busy during animation, and may not be called, so set the flag when the first animation begins
45767                 if (!i) {
45768                     boxAnim.listeners.beforeanimate = beforeAnimate;
45769                 }
45770                 if (me.animate.duration) {
45771                     boxAnim.duration = me.animate.duration;
45772                 }
45773                 comp = boxAnim.target;
45774                 delete boxAnim.target;
45775                 // Stop any currently running animation
45776                 comp.stopAnimation();
45777                 comp.animate(boxAnim);
45778             }
45779         }
45780     },
45781
45782     /**
45783      * @private
45784      * Called by onRender just before the child components are sized and positioned. This resizes the innerCt
45785      * to make sure all child items fit within it. We call this before sizing the children because if our child
45786      * items are larger than the previous innerCt size the browser will insert scrollbars and then remove them
45787      * again immediately afterwards, giving a performance hit.
45788      * Subclasses should provide an implementation.
45789      * @param {Object} currentSize The current height and width of the innerCt
45790      * @param {Array} calculations The new box calculations of all items to be laid out
45791      */
45792     updateInnerCtSize: function(tSize, calcs) {
45793         var me = this,
45794             mmax = Math.max,
45795             align = me.align,
45796             padding = me.padding,
45797             width = tSize.width,
45798             height = tSize.height,
45799             meta = calcs.meta,
45800             innerCtWidth,
45801             innerCtHeight;
45802
45803         if (me.direction == 'horizontal') {
45804             innerCtWidth = width;
45805             innerCtHeight = meta.maxSize + padding.top + padding.bottom + me.innerCt.getBorderWidth('tb');
45806
45807             if (align == 'stretch') {
45808                 innerCtHeight = height;
45809             }
45810             else if (align == 'middle') {
45811                 innerCtHeight = mmax(height, innerCtHeight);
45812             }
45813         } else {
45814             innerCtHeight = height;
45815             innerCtWidth = meta.maxSize + padding.left + padding.right + me.innerCt.getBorderWidth('lr');
45816
45817             if (align == 'stretch') {
45818                 innerCtWidth = width;
45819             }
45820             else if (align == 'center') {
45821                 innerCtWidth = mmax(width, innerCtWidth);
45822             }
45823         }
45824         me.getRenderTarget().setSize(innerCtWidth || undefined, innerCtHeight || undefined);
45825
45826         // If a calculated width has been found (and this only happens for auto-width vertical docked Components in old Microsoft browsers)
45827         // then, if the Component has not assumed the size of its content, set it to do so.
45828         if (meta.calculatedWidth && me.owner.el.getWidth() > meta.calculatedWidth) {
45829             me.owner.el.setWidth(meta.calculatedWidth);
45830         }
45831
45832         if (me.innerCt.dom.scrollTop) {
45833             me.innerCt.dom.scrollTop = 0;
45834         }
45835     },
45836
45837     /**
45838      * @private
45839      * This should be called after onLayout of any BoxLayout subclass. If the target's overflow is not set to 'hidden',
45840      * we need to lay out a second time because the scrollbars may have modified the height and width of the layout
45841      * target. Having a Box layout inside such a target is therefore not recommended.
45842      * @param {Object} previousTargetSize The size and height of the layout target before we just laid out
45843      * @param {Ext.container.Container} container The container
45844      * @param {Ext.core.Element} target The target element
45845      * @return True if the layout overflowed, and was reflowed in a secondary onLayout call.
45846      */
45847     handleTargetOverflow: function(previousTargetSize) {
45848         var target = this.getTarget(),
45849             overflow = target.getStyle('overflow'),
45850             newTargetSize;
45851
45852         if (overflow && overflow != 'hidden' && !this.adjustmentPass) {
45853             newTargetSize = this.getLayoutTargetSize();
45854             if (newTargetSize.width != previousTargetSize.width || newTargetSize.height != previousTargetSize.height) {
45855                 this.adjustmentPass = true;
45856                 this.onLayout();
45857                 return true;
45858             }
45859         }
45860
45861         delete this.adjustmentPass;
45862     },
45863
45864     // private
45865     isValidParent : function(item, target, position) {
45866         // Note: Box layouts do not care about order within the innerCt element because it's an absolutely positioning layout
45867         // We only care whether the item is a direct child of the innerCt element.
45868         var itemEl = item.el ? item.el.dom : Ext.getDom(item);
45869         return (itemEl && this.innerCt && itemEl.parentNode === this.innerCt.dom) || false;
45870     },
45871
45872     // Overridden method from AbstractContainer.
45873     // Used in the base AbstractLayout.beforeLayout method to render all items into.
45874     getRenderTarget: function() {
45875         if (!this.innerCt) {
45876             // the innerCt prevents wrapping and shuffling while the container is resizing
45877             this.innerCt = this.getTarget().createChild({
45878                 cls: this.innerCls,
45879                 role: 'presentation'
45880             });
45881             this.padding = Ext.util.Format.parseBox(this.padding);
45882         }
45883         return this.innerCt;
45884     },
45885
45886     // private
45887     renderItem: function(item, target) {
45888         this.callParent(arguments);
45889         var me = this,
45890             itemEl = item.getEl(),
45891             style = itemEl.dom.style,
45892             margins = item.margins || item.margin;
45893
45894         // Parse the item's margin/margins specification
45895         if (margins) {
45896             if (Ext.isString(margins) || Ext.isNumber(margins)) {
45897                 margins = Ext.util.Format.parseBox(margins);
45898             } else {
45899                 Ext.applyIf(margins, {top: 0, right: 0, bottom: 0, left: 0});
45900             }
45901         } else {
45902             margins = Ext.apply({}, me.defaultMargins);
45903         }
45904
45905         // Add any before/after CSS margins to the configured margins, and zero the CSS margins
45906         margins.top    += itemEl.getMargin('t');
45907         margins.right  += itemEl.getMargin('r');
45908         margins.bottom += itemEl.getMargin('b');
45909         margins.left   += itemEl.getMargin('l');
45910         style.marginTop = style.marginRight = style.marginBottom = style.marginLeft = '0';
45911
45912         // Item must reference calculated margins.
45913         item.margins = margins;
45914     },
45915
45916     /**
45917      * @private
45918      */
45919     destroy: function() {
45920         Ext.destroy(this.overflowHandler);
45921         this.callParent(arguments);
45922     }
45923 });
45924 /**
45925  * @class Ext.layout.container.HBox
45926  * @extends Ext.layout.container.Box
45927  * <p>A layout that arranges items horizontally across a Container. This layout optionally divides available horizontal
45928  * space between child items containing a numeric <code>flex</code> configuration.</p>
45929  * This layout may also be used to set the heights of child items by configuring it with the {@link #align} option.
45930  * {@img Ext.layout.container.HBox/Ext.layout.container.HBox.png Ext.layout.container.HBox container layout}
45931  * Example usage:
45932     Ext.create('Ext.Panel', {
45933         width: 500,
45934         height: 300,
45935         title: "HBoxLayout Panel",
45936         layout: {
45937             type: 'hbox',
45938             align: 'stretch'
45939         },
45940         renderTo: document.body,
45941         items: [{
45942             xtype: 'panel',
45943             title: 'Inner Panel One',
45944             flex: 2
45945         },{
45946             xtype: 'panel',
45947             title: 'Inner Panel Two',
45948             flex: 1
45949         },{
45950             xtype: 'panel',
45951             title: 'Inner Panel Three',
45952             flex: 1
45953         }]
45954     });
45955  */
45956 Ext.define('Ext.layout.container.HBox', {
45957
45958     /* Begin Definitions */
45959
45960     alias: ['layout.hbox'],
45961     extend: 'Ext.layout.container.Box',
45962     alternateClassName: 'Ext.layout.HBoxLayout',
45963     
45964     /* End Definitions */
45965
45966     /**
45967      * @cfg {String} align
45968      * Controls how the child items of the container are aligned. Acceptable configuration values for this
45969      * property are:
45970      * <div class="mdetail-params"><ul>
45971      * <li><b><tt>top</tt></b> : <b>Default</b><div class="sub-desc">child items are aligned vertically
45972      * at the <b>top</b> of the container</div></li>
45973      * <li><b><tt>middle</tt></b> : <div class="sub-desc">child items are aligned vertically in the
45974      * <b>middle</b> of the container</div></li>
45975      * <li><b><tt>stretch</tt></b> : <div class="sub-desc">child items are stretched vertically to fill
45976      * the height of the container</div></li>
45977      * <li><b><tt>stretchmax</tt></b> : <div class="sub-desc">child items are stretched vertically to
45978      * the height of the largest item.</div></li>
45979      * </ul></div>
45980      */
45981     align: 'top', // top, middle, stretch, strechmax
45982
45983     //@private
45984     alignCenteringString: 'middle',
45985
45986     type : 'hbox',
45987
45988     direction: 'horizontal',
45989
45990     // When creating an argument list to setSize, use this order
45991     parallelSizeIndex: 0,
45992     perpendicularSizeIndex: 1,
45993
45994     parallelPrefix: 'width',
45995     parallelPrefixCap: 'Width',
45996     parallelLT: 'l',
45997     parallelRB: 'r',
45998     parallelBefore: 'left',
45999     parallelBeforeCap: 'Left',
46000     parallelAfter: 'right',
46001     parallelPosition: 'x',
46002
46003     perpendicularPrefix: 'height',
46004     perpendicularPrefixCap: 'Height',
46005     perpendicularLT: 't',
46006     perpendicularRB: 'b',
46007     perpendicularLeftTop: 'top',
46008     perpendicularRightBottom: 'bottom',
46009     perpendicularPosition: 'y'
46010 });
46011 /**
46012  * @class Ext.layout.container.VBox
46013  * @extends Ext.layout.container.Box
46014  * <p>A layout that arranges items vertically down a Container. This layout optionally divides available vertical
46015  * space between child items containing a numeric <code>flex</code> configuration.</p>
46016  * This layout may also be used to set the widths of child items by configuring it with the {@link #align} option.
46017  * {@img Ext.layout.container.VBox/Ext.layout.container.VBox.png Ext.layout.container.VBox container layout}
46018  * Example usage:
46019         Ext.create('Ext.Panel', {
46020                 width: 500,
46021                 height: 400,
46022                 title: "VBoxLayout Panel",
46023                 layout: {                        
46024                         type: 'vbox',
46025                         align: 'center'
46026                 },
46027                 renderTo: document.body,
46028                 items: [{                        
46029                         xtype: 'panel',
46030                         title: 'Inner Panel One',
46031                         width: 250,
46032                         flex: 2                      
46033                 },{
46034                         xtype: 'panel',
46035                         title: 'Inner Panel Two',
46036                         width: 250,                     
46037                         flex: 4
46038                 },{
46039                         xtype: 'panel',
46040                         title: 'Inner Panel Three',
46041                         width: '50%',                   
46042                         flex: 4
46043                 }]
46044         });
46045  */
46046 Ext.define('Ext.layout.container.VBox', {
46047
46048     /* Begin Definitions */
46049
46050     alias: ['layout.vbox'],
46051     extend: 'Ext.layout.container.Box',
46052     alternateClassName: 'Ext.layout.VBoxLayout',
46053     
46054     /* End Definitions */
46055
46056     /**
46057      * @cfg {String} align
46058      * Controls how the child items of the container are aligned. Acceptable configuration values for this
46059      * property are:
46060      * <div class="mdetail-params"><ul>
46061      * <li><b><tt>left</tt></b> : <b>Default</b><div class="sub-desc">child items are aligned horizontally
46062      * at the <b>left</b> side of the container</div></li>
46063      * <li><b><tt>center</tt></b> : <div class="sub-desc">child items are aligned horizontally at the
46064      * <b>mid-width</b> of the container</div></li>
46065      * <li><b><tt>stretch</tt></b> : <div class="sub-desc">child items are stretched horizontally to fill
46066      * the width of the container</div></li>
46067      * <li><b><tt>stretchmax</tt></b> : <div class="sub-desc">child items are stretched horizontally to
46068      * the size of the largest item.</div></li>
46069      * </ul></div>
46070      */
46071     align : 'left', // left, center, stretch, strechmax
46072
46073     //@private
46074     alignCenteringString: 'center',
46075
46076     type: 'vbox',
46077
46078     direction: 'vertical',
46079
46080     // When creating an argument list to setSize, use this order
46081     parallelSizeIndex: 1,
46082     perpendicularSizeIndex: 0,
46083
46084     parallelPrefix: 'height',
46085     parallelPrefixCap: 'Height',
46086     parallelLT: 't',
46087     parallelRB: 'b',
46088     parallelBefore: 'top',
46089     parallelBeforeCap: 'Top',
46090     parallelAfter: 'bottom',
46091     parallelPosition: 'y',
46092
46093     perpendicularPrefix: 'width',
46094     perpendicularPrefixCap: 'Width',
46095     perpendicularLT: 'l',
46096     perpendicularRB: 'r',
46097     perpendicularLeftTop: 'left',
46098     perpendicularRightBottom: 'right',
46099     perpendicularPosition: 'x'
46100 });
46101 /**
46102  * @class Ext.FocusManager
46103
46104 The FocusManager is responsible for globally:
46105
46106 1. Managing component focus
46107 2. Providing basic keyboard navigation
46108 3. (optional) Provide a visual cue for focused components, in the form of a focus ring/frame.
46109
46110 To activate the FocusManager, simply call {@link #enable `Ext.FocusManager.enable();`}. In turn, you may
46111 deactivate the FocusManager by subsequently calling {@link #disable `Ext.FocusManager.disable();`}.  The
46112 FocusManager is disabled by default.
46113
46114 To enable the optional focus frame, pass `true` or `{focusFrame: true}` to {@link #enable}.
46115
46116 Another feature of the FocusManager is to provide basic keyboard focus navigation scoped to any {@link Ext.container.Container}
46117 that would like to have navigation between its child {@link Ext.Component}'s. The {@link Ext.container.Container} can simply
46118 call {@link #subscribe Ext.FocusManager.subscribe} to take advantage of this feature, and can at any time call
46119 {@link #unsubscribe Ext.FocusManager.unsubscribe} to turn the navigation off.
46120
46121  * @singleton
46122  * @markdown
46123  * @author Jarred Nicholls <jarred@sencha.com>
46124  * @docauthor Jarred Nicholls <jarred@sencha.com>
46125  */
46126 Ext.define('Ext.FocusManager', {
46127     singleton: true,
46128     alternateClassName: 'Ext.FocusMgr',
46129
46130     mixins: {
46131         observable: 'Ext.util.Observable'
46132     },
46133
46134     requires: [
46135         'Ext.ComponentManager',
46136         'Ext.ComponentQuery',
46137         'Ext.util.HashMap',
46138         'Ext.util.KeyNav'
46139     ],
46140
46141     /**
46142      * @property {Boolean} enabled
46143      * Whether or not the FocusManager is currently enabled
46144      */
46145     enabled: false,
46146
46147     /**
46148      * @property {Ext.Component} focusedCmp
46149      * The currently focused component. Defaults to `undefined`.
46150      * @markdown
46151      */
46152
46153     focusElementCls: Ext.baseCSSPrefix + 'focus-element',
46154
46155     focusFrameCls: Ext.baseCSSPrefix + 'focus-frame',
46156
46157     /**
46158      * @property {Array} whitelist
46159      * A list of xtypes that should ignore certain navigation input keys and
46160      * allow for the default browser event/behavior. These input keys include:
46161      *
46162      * 1. Backspace
46163      * 2. Delete
46164      * 3. Left
46165      * 4. Right
46166      * 5. Up
46167      * 6. Down
46168      *
46169      * The FocusManager will not attempt to navigate when a component is an xtype (or descendents thereof)
46170      * that belongs to this whitelist. E.g., an {@link Ext.form.field.Text} should allow
46171      * the user to move the input cursor left and right, and to delete characters, etc.
46172      *
46173      * This whitelist currently defaults to `['textfield']`.
46174      * @markdown
46175      */
46176     whitelist: [
46177         'textfield'
46178     ],
46179
46180     tabIndexWhitelist: [
46181         'a',
46182         'button',
46183         'embed',
46184         'frame',
46185         'iframe',
46186         'img',
46187         'input',
46188         'object',
46189         'select',
46190         'textarea'
46191     ],
46192
46193     constructor: function() {
46194         var me = this,
46195             CQ = Ext.ComponentQuery;
46196
46197         me.addEvents(
46198             /**
46199              * @event beforecomponentfocus
46200              * Fires before a component becomes focused. Return `false` to prevent
46201              * the component from gaining focus.
46202              * @param {Ext.FocusManager} fm A reference to the FocusManager singleton
46203              * @param {Ext.Component} cmp The component that is being focused
46204              * @param {Ext.Component} previousCmp The component that was previously focused,
46205              * or `undefined` if there was no previously focused component.
46206              * @markdown
46207              */
46208             'beforecomponentfocus',
46209
46210             /**
46211              * @event componentfocus
46212              * Fires after a component becomes focused.
46213              * @param {Ext.FocusManager} fm A reference to the FocusManager singleton
46214              * @param {Ext.Component} cmp The component that has been focused
46215              * @param {Ext.Component} previousCmp The component that was previously focused,
46216              * or `undefined` if there was no previously focused component.
46217              * @markdown
46218              */
46219             'componentfocus',
46220
46221             /**
46222              * @event disable
46223              * Fires when the FocusManager is disabled
46224              * @param {Ext.FocusManager} fm A reference to the FocusManager singleton
46225              */
46226             'disable',
46227
46228             /**
46229              * @event enable
46230              * Fires when the FocusManager is enabled
46231              * @param {Ext.FocusManager} fm A reference to the FocusManager singleton
46232              */
46233             'enable'
46234         );
46235
46236         // Setup KeyNav that's bound to document to catch all
46237         // unhandled/bubbled key events for navigation
46238         me.keyNav = Ext.create('Ext.util.KeyNav', Ext.getDoc(), {
46239             disabled: true,
46240             scope: me,
46241
46242             backspace: me.focusLast,
46243             enter: me.navigateIn,
46244             esc: me.navigateOut,
46245             tab: me.navigateSiblings
46246
46247             //space: me.navigateIn,
46248             //del: me.focusLast,
46249             //left: me.navigateSiblings,
46250             //right: me.navigateSiblings,
46251             //down: me.navigateSiblings,
46252             //up: me.navigateSiblings
46253         });
46254
46255         me.focusData = {};
46256         me.subscribers = Ext.create('Ext.util.HashMap');
46257         me.focusChain = {};
46258
46259         // Setup some ComponentQuery pseudos
46260         Ext.apply(CQ.pseudos, {
46261             focusable: function(cmps) {
46262                 var len = cmps.length,
46263                     results = [],
46264                     i = 0,
46265                     c,
46266
46267                     isFocusable = function(x) {
46268                         return x && x.focusable !== false && CQ.is(x, '[rendered]:not([destroying]):not([isDestroyed]):not([disabled]){isVisible(true)}{el && c.el.dom && c.el.isVisible()}');
46269                     };
46270
46271                 for (; i < len; i++) {
46272                     c = cmps[i];
46273                     if (isFocusable(c)) {
46274                         results.push(c);
46275                     }
46276                 }
46277
46278                 return results;
46279             },
46280
46281             nextFocus: function(cmps, idx, step) {
46282                 step = step || 1;
46283                 idx = parseInt(idx, 10);
46284
46285                 var len = cmps.length,
46286                     i = idx + step,
46287                     c;
46288
46289                 for (; i != idx; i += step) {
46290                     if (i >= len) {
46291                         i = 0;
46292                     } else if (i < 0) {
46293                         i = len - 1;
46294                     }
46295
46296                     c = cmps[i];
46297                     if (CQ.is(c, ':focusable')) {
46298                         return [c];
46299                     } else if (c.placeholder && CQ.is(c.placeholder, ':focusable')) {
46300                         return [c.placeholder];
46301                     }
46302                 }
46303
46304                 return [];
46305             },
46306
46307             prevFocus: function(cmps, idx) {
46308                 return this.nextFocus(cmps, idx, -1);
46309             },
46310
46311             root: function(cmps) {
46312                 var len = cmps.length,
46313                     results = [],
46314                     i = 0,
46315                     c;
46316
46317                 for (; i < len; i++) {
46318                     c = cmps[i];
46319                     if (!c.ownerCt) {
46320                         results.push(c);
46321                     }
46322                 }
46323
46324                 return results;
46325             }
46326         });
46327     },
46328
46329     /**
46330      * Adds the specified xtype to the {@link #whitelist}.
46331      * @param {String/Array} xtype Adds the xtype(s) to the {@link #whitelist}.
46332      */
46333     addXTypeToWhitelist: function(xtype) {
46334         var me = this;
46335
46336         if (Ext.isArray(xtype)) {
46337             Ext.Array.forEach(xtype, me.addXTypeToWhitelist, me);
46338             return;
46339         }
46340
46341         if (!Ext.Array.contains(me.whitelist, xtype)) {
46342             me.whitelist.push(xtype);
46343         }
46344     },
46345
46346     clearComponent: function(cmp) {
46347         clearTimeout(this.cmpFocusDelay);
46348         if (!cmp.isDestroyed) {
46349             cmp.blur();
46350         }
46351     },
46352
46353     /**
46354      * Disables the FocusManager by turning of all automatic focus management and keyboard navigation
46355      */
46356     disable: function() {
46357         var me = this;
46358
46359         if (!me.enabled) {
46360             return;
46361         }
46362
46363         delete me.options;
46364         me.enabled = false;
46365
46366         Ext.ComponentManager.all.un('add', me.onComponentCreated, me);
46367
46368         me.removeDOM();
46369
46370         // Stop handling key navigation
46371         me.keyNav.disable();
46372
46373         // disable focus for all components
46374         me.setFocusAll(false);
46375
46376         me.fireEvent('disable', me);
46377     },
46378
46379     /**
46380      * Enables the FocusManager by turning on all automatic focus management and keyboard navigation
46381      * @param {Boolean/Object} options Either `true`/`false` to turn on the focus frame, or an object of the following options:
46382         - focusFrame : Boolean
46383             `true` to show the focus frame around a component when it is focused. Defaults to `false`.
46384      * @markdown
46385      */
46386     enable: function(options) {
46387         var me = this;
46388
46389         if (options === true) {
46390             options = { focusFrame: true };
46391         }
46392         me.options = options = options || {};
46393
46394         if (me.enabled) {
46395             return;
46396         }
46397
46398         // Handle components that are newly added after we are enabled
46399         Ext.ComponentManager.all.on('add', me.onComponentCreated, me);
46400
46401         me.initDOM(options);
46402
46403         // Start handling key navigation
46404         me.keyNav.enable();
46405
46406         // enable focus for all components
46407         me.setFocusAll(true, options);
46408
46409         // Finally, let's focus our global focus el so we start fresh
46410         me.focusEl.focus();
46411         delete me.focusedCmp;
46412
46413         me.enabled = true;
46414         me.fireEvent('enable', me);
46415     },
46416
46417     focusLast: function(e) {
46418         var me = this;
46419
46420         if (me.isWhitelisted(me.focusedCmp)) {
46421             return true;
46422         }
46423
46424         // Go back to last focused item
46425         if (me.previousFocusedCmp) {
46426             me.previousFocusedCmp.focus();
46427         }
46428     },
46429
46430     getRootComponents: function() {
46431         var me = this,
46432             CQ = Ext.ComponentQuery,
46433             inline = CQ.query(':focusable:root:not([floating])'),
46434             floating = CQ.query(':focusable:root[floating]');
46435
46436         // Floating items should go to the top of our root stack, and be ordered
46437         // by their z-index (highest first)
46438         floating.sort(function(a, b) {
46439             return a.el.getZIndex() > b.el.getZIndex();
46440         });
46441
46442         return floating.concat(inline);
46443     },
46444
46445     initDOM: function(options) {
46446         var me = this,
46447             sp = '&#160',
46448             cls = me.focusFrameCls;
46449
46450         if (!Ext.isReady) {
46451             Ext.onReady(me.initDOM, me);
46452             return;
46453         }
46454
46455         // Create global focus element
46456         if (!me.focusEl) {
46457             me.focusEl = Ext.getBody().createChild({
46458                 tabIndex: '-1',
46459                 cls: me.focusElementCls,
46460                 html: sp
46461             });
46462         }
46463
46464         // Create global focus frame
46465         if (!me.focusFrame && options.focusFrame) {
46466             me.focusFrame = Ext.getBody().createChild({
46467                 cls: cls,
46468                 children: [
46469                     { cls: cls + '-top' },
46470                     { cls: cls + '-bottom' },
46471                     { cls: cls + '-left' },
46472                     { cls: cls + '-right' }
46473                 ],
46474                 style: 'top: -100px; left: -100px;'
46475             });
46476             me.focusFrame.setVisibilityMode(Ext.core.Element.DISPLAY);
46477             me.focusFrameWidth = me.focusFrame.child('.' + cls + '-top').getHeight();
46478             me.focusFrame.hide().setLeftTop(0, 0);
46479         }
46480     },
46481
46482     isWhitelisted: function(cmp) {
46483         return cmp && Ext.Array.some(this.whitelist, function(x) {
46484             return cmp.isXType(x);
46485         });
46486     },
46487
46488     navigateIn: function(e) {
46489         var me = this,
46490             focusedCmp = me.focusedCmp,
46491             rootCmps,
46492             firstChild;
46493
46494         if (!focusedCmp) {
46495             // No focus yet, so focus the first root cmp on the page
46496             rootCmps = me.getRootComponents();
46497             if (rootCmps.length) {
46498                 rootCmps[0].focus();
46499             }
46500         } else {
46501             // Drill into child ref items of the focused cmp, if applicable.
46502             // This works for any Component with a getRefItems implementation.
46503             firstChild = Ext.ComponentQuery.query('>:focusable', focusedCmp)[0];
46504             if (firstChild) {
46505                 firstChild.focus();
46506             } else {
46507                 // Let's try to fire a click event, as if it came from the mouse
46508                 if (Ext.isFunction(focusedCmp.onClick)) {
46509                     e.button = 0;
46510                     focusedCmp.onClick(e);
46511                     focusedCmp.focus();
46512                 }
46513             }
46514         }
46515     },
46516
46517     navigateOut: function(e) {
46518         var me = this,
46519             parent;
46520
46521         if (!me.focusedCmp || !(parent = me.focusedCmp.up(':focusable'))) {
46522             me.focusEl.focus();
46523             return;
46524         }
46525
46526         parent.focus();
46527     },
46528
46529     navigateSiblings: function(e, source, parent) {
46530         var me = this,
46531             src = source || me,
46532             key = e.getKey(),
46533             EO = Ext.EventObject,
46534             goBack = e.shiftKey || key == EO.LEFT || key == EO.UP,
46535             checkWhitelist = key == EO.LEFT || key == EO.RIGHT || key == EO.UP || key == EO.DOWN,
46536             nextSelector = goBack ? 'prev' : 'next',
46537             idx, next, focusedCmp;
46538
46539         focusedCmp = (src.focusedCmp && src.focusedCmp.comp) || src.focusedCmp;
46540         if (!focusedCmp && !parent) {
46541             return;
46542         }
46543
46544         if (checkWhitelist && me.isWhitelisted(focusedCmp)) {
46545             return true;
46546         }
46547
46548         parent = parent || focusedCmp.up();
46549         if (parent) {
46550             idx = focusedCmp ? Ext.Array.indexOf(parent.getRefItems(), focusedCmp) : -1;
46551             next = Ext.ComponentQuery.query('>:' + nextSelector + 'Focus(' + idx + ')', parent)[0];
46552             if (next && focusedCmp !== next) {
46553                 next.focus();
46554                 return next;
46555             }
46556         }
46557     },
46558
46559     onComponentBlur: function(cmp, e) {
46560         var me = this;
46561
46562         if (me.focusedCmp === cmp) {
46563             me.previousFocusedCmp = cmp;
46564             delete me.focusedCmp;
46565         }
46566
46567         if (me.focusFrame) {
46568             me.focusFrame.hide();
46569         }
46570     },
46571
46572     onComponentCreated: function(hash, id, cmp) {
46573         this.setFocus(cmp, true, this.options);
46574     },
46575
46576     onComponentDestroy: function(cmp) {
46577         this.setFocus(cmp, false);
46578     },
46579
46580     onComponentFocus: function(cmp, e) {
46581         var me = this,
46582             chain = me.focusChain;
46583
46584         if (!Ext.ComponentQuery.is(cmp, ':focusable')) {
46585             me.clearComponent(cmp);
46586
46587             // Check our focus chain, so we don't run into a never ending recursion
46588             // If we've attempted (unsuccessfully) to focus this component before,
46589             // then we're caught in a loop of child->parent->...->child and we
46590             // need to cut the loop off rather than feed into it.
46591             if (chain[cmp.id]) {
46592                 return;
46593             }
46594
46595             // Try to focus the parent instead
46596             var parent = cmp.up();
46597             if (parent) {
46598                 // Add component to our focus chain to detect infinite focus loop
46599                 // before we fire off an attempt to focus our parent.
46600                 // See the comments above.
46601                 chain[cmp.id] = true;
46602                 parent.focus();
46603             }
46604
46605             return;
46606         }
46607
46608         // Clear our focus chain when we have a focusable component
46609         me.focusChain = {};
46610
46611         // Defer focusing for 90ms so components can do a layout/positioning
46612         // and give us an ability to buffer focuses
46613         clearTimeout(me.cmpFocusDelay);
46614         if (arguments.length !== 2) {
46615             me.cmpFocusDelay = Ext.defer(me.onComponentFocus, 90, me, [cmp, e]);
46616             return;
46617         }
46618
46619         if (me.fireEvent('beforecomponentfocus', me, cmp, me.previousFocusedCmp) === false) {
46620             me.clearComponent(cmp);
46621             return;
46622         }
46623
46624         me.focusedCmp = cmp;
46625
46626         // If we have a focus frame, show it around the focused component
46627         if (me.shouldShowFocusFrame(cmp)) {
46628             var cls = '.' + me.focusFrameCls + '-',
46629                 ff = me.focusFrame,
46630                 fw = me.focusFrameWidth,
46631                 box = cmp.el.getPageBox(),
46632
46633             // Size the focus frame's t/b/l/r according to the box
46634             // This leaves a hole in the middle of the frame so user
46635             // interaction w/ the mouse can continue
46636                 bt = box.top,
46637                 bl = box.left,
46638                 bw = box.width,
46639                 bh = box.height,
46640                 ft = ff.child(cls + 'top'),
46641                 fb = ff.child(cls + 'bottom'),
46642                 fl = ff.child(cls + 'left'),
46643                 fr = ff.child(cls + 'right');
46644
46645             ft.setWidth(bw - 2).setLeftTop(bl + 1, bt);
46646             fb.setWidth(bw - 2).setLeftTop(bl + 1, bt + bh - fw);
46647             fl.setHeight(bh - 2).setLeftTop(bl, bt + 1);
46648             fr.setHeight(bh - 2).setLeftTop(bl + bw - fw, bt + 1);
46649
46650             ff.show();
46651         }
46652
46653         me.fireEvent('componentfocus', me, cmp, me.previousFocusedCmp);
46654     },
46655
46656     onComponentHide: function(cmp) {
46657         var me = this,
46658             CQ = Ext.ComponentQuery,
46659             cmpHadFocus = false,
46660             focusedCmp,
46661             parent;
46662
46663         if (me.focusedCmp) {
46664             focusedCmp = CQ.query('[id=' + me.focusedCmp.id + ']', cmp)[0];
46665             cmpHadFocus = me.focusedCmp.id === cmp.id || focusedCmp;
46666
46667             if (focusedCmp) {
46668                 me.clearComponent(focusedCmp);
46669             }
46670         }
46671
46672         me.clearComponent(cmp);
46673
46674         if (cmpHadFocus) {
46675             parent = CQ.query('^:focusable', cmp)[0];
46676             if (parent) {
46677                 parent.focus();
46678             }
46679         }
46680     },
46681
46682     removeDOM: function() {
46683         var me = this;
46684
46685         // If we are still enabled globally, or there are still subscribers
46686         // then we will halt here, since our DOM stuff is still being used
46687         if (me.enabled || me.subscribers.length) {
46688             return;
46689         }
46690
46691         Ext.destroy(
46692             me.focusEl,
46693             me.focusFrame
46694         );
46695         delete me.focusEl;
46696         delete me.focusFrame;
46697         delete me.focusFrameWidth;
46698     },
46699
46700     /**
46701      * Removes the specified xtype from the {@link #whitelist}.
46702      * @param {String/Array} xtype Removes the xtype(s) from the {@link #whitelist}.
46703      */
46704     removeXTypeFromWhitelist: function(xtype) {
46705         var me = this;
46706
46707         if (Ext.isArray(xtype)) {
46708             Ext.Array.forEach(xtype, me.removeXTypeFromWhitelist, me);
46709             return;
46710         }
46711
46712         Ext.Array.remove(me.whitelist, xtype);
46713     },
46714
46715     setFocus: function(cmp, focusable, options) {
46716         var me = this,
46717             el, dom, data,
46718
46719             needsTabIndex = function(n) {
46720                 return !Ext.Array.contains(me.tabIndexWhitelist, n.tagName.toLowerCase())
46721                     && n.tabIndex <= 0;
46722             };
46723
46724         options = options || {};
46725
46726         // Come back and do this after the component is rendered
46727         if (!cmp.rendered) {
46728             cmp.on('afterrender', Ext.pass(me.setFocus, arguments, me), me, { single: true });
46729             return;
46730         }
46731
46732         el = cmp.getFocusEl();
46733         dom = el.dom;
46734
46735         // Decorate the component's focus el for focus-ability
46736         if ((focusable && !me.focusData[cmp.id]) || (!focusable && me.focusData[cmp.id])) {
46737             if (focusable) {
46738                 data = {
46739                     focusFrame: options.focusFrame
46740                 };
46741
46742                 // Only set -1 tabIndex if we need it
46743                 // inputs, buttons, and anchor tags do not need it,
46744                 // and neither does any DOM that has it set already
46745                 // programmatically or in markup.
46746                 if (needsTabIndex(dom)) {
46747                     data.tabIndex = dom.tabIndex;
46748                     dom.tabIndex = -1;
46749                 }
46750
46751                 el.on({
46752                     focus: data.focusFn = Ext.bind(me.onComponentFocus, me, [cmp], 0),
46753                     blur: data.blurFn = Ext.bind(me.onComponentBlur, me, [cmp], 0),
46754                     scope: me
46755                 });
46756                 cmp.on({
46757                     hide: me.onComponentHide,
46758                     close: me.onComponentHide,
46759                     beforedestroy: me.onComponentDestroy,
46760                     scope: me
46761                 });
46762
46763                 me.focusData[cmp.id] = data;
46764             } else {
46765                 data = me.focusData[cmp.id];
46766                 if ('tabIndex' in data) {
46767                     dom.tabIndex = data.tabIndex;
46768                 }
46769                 el.un('focus', data.focusFn, me);
46770                 el.un('blur', data.blurFn, me);
46771                 cmp.un('hide', me.onComponentHide, me);
46772                 cmp.un('close', me.onComponentHide, me);
46773                 cmp.un('beforedestroy', me.onComponentDestroy, me);
46774
46775                 delete me.focusData[cmp.id];
46776             }
46777         }
46778     },
46779
46780     setFocusAll: function(focusable, options) {
46781         var me = this,
46782             cmps = Ext.ComponentManager.all.getArray(),
46783             len = cmps.length,
46784             cmp,
46785             i = 0;
46786
46787         for (; i < len; i++) {
46788             me.setFocus(cmps[i], focusable, options);
46789         }
46790     },
46791
46792     setupSubscriberKeys: function(container, keys) {
46793         var me = this,
46794             el = container.getFocusEl(),
46795             scope = keys.scope,
46796             handlers = {
46797                 backspace: me.focusLast,
46798                 enter: me.navigateIn,
46799                 esc: me.navigateOut,
46800                 scope: me
46801             },
46802
46803             navSiblings = function(e) {
46804                 if (me.focusedCmp === container) {
46805                     // Root the sibling navigation to this container, so that we
46806                     // can automatically dive into the container, rather than forcing
46807                     // the user to hit the enter key to dive in.
46808                     return me.navigateSiblings(e, me, container);
46809                 } else {
46810                     return me.navigateSiblings(e);
46811                 }
46812             };
46813
46814         Ext.iterate(keys, function(key, cb) {
46815             handlers[key] = function(e) {
46816                 var ret = navSiblings(e);
46817
46818                 if (Ext.isFunction(cb) && cb.call(scope || container, e, ret) === true) {
46819                     return true;
46820                 }
46821
46822                 return ret;
46823             };
46824         }, me);
46825
46826         return Ext.create('Ext.util.KeyNav', el, handlers);
46827     },
46828
46829     shouldShowFocusFrame: function(cmp) {
46830         var me = this,
46831             opts = me.options || {};
46832
46833         if (!me.focusFrame || !cmp) {
46834             return false;
46835         }
46836
46837         // Global trumps
46838         if (opts.focusFrame) {
46839             return true;
46840         }
46841
46842         if (me.focusData[cmp.id].focusFrame) {
46843             return true;
46844         }
46845
46846         return false;
46847     },
46848
46849     /**
46850      * Subscribes an {@link Ext.container.Container} to provide basic keyboard focus navigation between its child {@link Ext.Component}'s.
46851      * @param {Ext.container.Container} container A reference to the {@link Ext.container.Container} on which to enable keyboard functionality and focus management.
46852      * @param {Boolean/Object} options An object of the following options:
46853         - keys : Array/Object
46854             An array containing the string names of navigation keys to be supported. The allowed values are:
46855
46856             - 'left'
46857             - 'right'
46858             - 'up'
46859             - 'down'
46860
46861             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.:
46862
46863                 {
46864                     left: this.onLeftKey,
46865                     right: this.onRightKey,
46866                     scope: this
46867                 }
46868
46869         - focusFrame : Boolean (optional)
46870             `true` to show the focus frame around a component when it is focused. Defaults to `false`.
46871      * @markdown
46872      */
46873     subscribe: function(container, options) {
46874         var me = this,
46875             EA = Ext.Array,
46876             data = {},
46877             subs = me.subscribers,
46878
46879             // Recursively add focus ability as long as a descendent container isn't
46880             // itself subscribed to the FocusManager, or else we'd have unwanted side
46881             // effects for subscribing a descendent container twice.
46882             safeSetFocus = function(cmp) {
46883                 if (cmp.isContainer && !subs.containsKey(cmp.id)) {
46884                     EA.forEach(cmp.query('>'), safeSetFocus);
46885                     me.setFocus(cmp, true, options);
46886                     cmp.on('add', data.onAdd, me);
46887                 } else if (!cmp.isContainer) {
46888                     me.setFocus(cmp, true, options);
46889                 }
46890             };
46891
46892         // We only accept containers
46893         if (!container || !container.isContainer) {
46894             return;
46895         }
46896
46897         if (!container.rendered) {
46898             container.on('afterrender', Ext.pass(me.subscribe, arguments, me), me, { single: true });
46899             return;
46900         }
46901
46902         // Init the DOM, incase this is the first time it will be used
46903         me.initDOM(options);
46904
46905         // Create key navigation for subscriber based on keys option
46906         data.keyNav = me.setupSubscriberKeys(container, options.keys);
46907
46908         // We need to keep track of components being added to our subscriber
46909         // and any containers nested deeply within it (omg), so let's do that.
46910         // Components that are removed are globally handled.
46911         // Also keep track of destruction of our container for auto-unsubscribe.
46912         data.onAdd = function(ct, cmp, idx) {
46913             safeSetFocus(cmp);
46914         };
46915         container.on('beforedestroy', me.unsubscribe, me);
46916
46917         // Now we setup focusing abilities for the container and all its components
46918         safeSetFocus(container);
46919
46920         // Add to our subscribers list
46921         subs.add(container.id, data);
46922     },
46923
46924     /**
46925      * Unsubscribes an {@link Ext.container.Container} from keyboard focus management.
46926      * @param {Ext.container.Container} container A reference to the {@link Ext.container.Container} to unsubscribe from the FocusManager.
46927      * @markdown
46928      */
46929     unsubscribe: function(container) {
46930         var me = this,
46931             EA = Ext.Array,
46932             subs = me.subscribers,
46933             data,
46934
46935             // Recursively remove focus ability as long as a descendent container isn't
46936             // itself subscribed to the FocusManager, or else we'd have unwanted side
46937             // effects for unsubscribing an ancestor container.
46938             safeSetFocus = function(cmp) {
46939                 if (cmp.isContainer && !subs.containsKey(cmp.id)) {
46940                     EA.forEach(cmp.query('>'), safeSetFocus);
46941                     me.setFocus(cmp, false);
46942                     cmp.un('add', data.onAdd, me);
46943                 } else if (!cmp.isContainer) {
46944                     me.setFocus(cmp, false);
46945                 }
46946             };
46947
46948         if (!container || !subs.containsKey(container.id)) {
46949             return;
46950         }
46951
46952         data = subs.get(container.id);
46953         data.keyNav.destroy();
46954         container.un('beforedestroy', me.unsubscribe, me);
46955         subs.removeAtKey(container.id);
46956         safeSetFocus(container);
46957         me.removeDOM();
46958     }
46959 });
46960 /**
46961  * @class Ext.toolbar.Toolbar
46962  * @extends Ext.container.Container
46963
46964 Basic Toolbar class. Although the {@link Ext.container.Container#defaultType defaultType} for Toolbar is {@link Ext.button.Button button}, Toolbar 
46965 elements (child items for the Toolbar container) may be virtually any type of Component. Toolbar elements can be created explicitly via their 
46966 constructors, or implicitly via their xtypes, and can be {@link #add}ed dynamically.
46967
46968 __Some items have shortcut strings for creation:__
46969
46970 | Shortcut | xtype         | Class                         | Description                                        |
46971 |:---------|:--------------|:------------------------------|:---------------------------------------------------|
46972 | `->`     | `tbspacer`    | {@link Ext.toolbar.Fill}      | begin using the right-justified button container   |
46973 | `-`      | `tbseparator` | {@link Ext.toolbar.Separator} | add a vertical separator bar between toolbar items |
46974 | ` `      | `tbspacer`    | {@link Ext.toolbar.Spacer}    | add horiztonal space between elements              |
46975
46976 {@img Ext.toolbar.Toolbar/Ext.toolbar.Toolbar1.png Toolbar component}
46977 Example usage:
46978
46979     Ext.create('Ext.toolbar.Toolbar", {
46980         renderTo: document.body,
46981         width   : 500,
46982         items: [
46983             {
46984                 // xtype: 'button', // default for Toolbars
46985                 text: 'Button'
46986             },
46987             {
46988                 xtype: 'splitbutton',
46989                 text : 'Split Button'
46990             },
46991             // begin using the right-justified button container
46992             '->', // same as {xtype: 'tbfill'}, // Ext.toolbar.Fill
46993             {
46994                 xtype    : 'textfield',
46995                 name     : 'field1',
46996                 emptyText: 'enter search term'
46997             },
46998             // add a vertical separator bar between toolbar items
46999             '-', // same as {xtype: 'tbseparator'} to create Ext.toolbar.Separator
47000             'text 1', // same as {xtype: 'tbtext', text: 'text1'} to create Ext.toolbar.TextItem
47001             {xtype: 'tbspacer'},// same as ' ' to create Ext.toolbar.Spacer
47002             'text 2',
47003             {xtype: 'tbspacer', width: 50}, // add a 50px space
47004             'text 3'
47005         ]
47006     });
47007
47008 Toolbars have {@link #enable} and {@link #disable} methods which when called, will enable/disable all items within your toolbar.
47009
47010 {@img Ext.toolbar.Toolbar/Ext.toolbar.Toolbar2.png Toolbar component}
47011 Example usage:
47012
47013     Ext.create('Ext.toolbar.Toolbar', {
47014         renderTo: document.body,
47015         width   : 400,
47016         items: [
47017             {
47018                 text: 'Button'
47019             },
47020             {
47021                 xtype: 'splitbutton',
47022                 text : 'Split Button'
47023             },
47024             '->',
47025             {
47026                 xtype    : 'textfield',
47027                 name     : 'field1',
47028                 emptyText: 'enter search term'
47029             }
47030         ]
47031     });
47032
47033 {@img Ext.toolbar.Toolbar/Ext.toolbar.Toolbar3.png Toolbar component}
47034 Example usage:
47035     
47036     var enableBtn = Ext.create('Ext.button.Button', {
47037         text    : 'Enable All Items',
47038         disabled: true,
47039         scope   : this,
47040         handler : function() {
47041             //disable the enable button and enable the disable button
47042             enableBtn.disable();
47043             disableBtn.enable();
47044             
47045             //enable the toolbar
47046             toolbar.enable();
47047         }
47048     });
47049     
47050     var disableBtn = Ext.create('Ext.button.Button', {
47051         text    : 'Disable All Items',
47052         scope   : this,
47053         handler : function() {
47054             //enable the enable button and disable button
47055             disableBtn.disable();
47056             enableBtn.enable();
47057             
47058             //disable the toolbar
47059             toolbar.disable();
47060         }
47061     });
47062     
47063     var toolbar = Ext.create('Ext.toolbar.Toolbar', {
47064         renderTo: document.body,
47065         width   : 400,
47066         margin  : '5 0 0 0',
47067         items   : [enableBtn, disableBtn]
47068     });
47069
47070 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 
47071 which remove all items within the toolbar.
47072
47073 {@img Ext.toolbar.Toolbar/Ext.toolbar.Toolbar4.png Toolbar component}
47074 Example usage:
47075
47076     var toolbar = Ext.create('Ext.toolbar.Toolbar', {
47077         renderTo: document.body,
47078         width   : 700,
47079         items: [
47080             {
47081                 text: 'Example Button'
47082             }
47083         ]
47084     });
47085     
47086     var addedItems = [];
47087     
47088     Ext.create('Ext.toolbar.Toolbar', {
47089         renderTo: document.body,
47090         width   : 700,
47091         margin  : '5 0 0 0',
47092         items   : [
47093             {
47094                 text   : 'Add a button',
47095                 scope  : this,
47096                 handler: function() {
47097                     var text = prompt('Please enter the text for your button:');
47098                     addedItems.push(toolbar.add({
47099                         text: text
47100                     }));
47101                 }
47102             },
47103             {
47104                 text   : 'Add a text item',
47105                 scope  : this,
47106                 handler: function() {
47107                     var text = prompt('Please enter the text for your item:');
47108                     addedItems.push(toolbar.add(text));
47109                 }
47110             },
47111             {
47112                 text   : 'Add a toolbar seperator',
47113                 scope  : this,
47114                 handler: function() {
47115                     addedItems.push(toolbar.add('-'));
47116                 }
47117             },
47118             {
47119                 text   : 'Add a toolbar spacer',
47120                 scope  : this,
47121                 handler: function() {
47122                     addedItems.push(toolbar.add('->'));
47123                 }
47124             },
47125             '->',
47126             {
47127                 text   : 'Remove last inserted item',
47128                 scope  : this,
47129                 handler: function() {
47130                     if (addedItems.length) {
47131                         toolbar.remove(addedItems.pop());
47132                     } else if (toolbar.items.length) {
47133                         toolbar.remove(toolbar.items.last());
47134                     } else {
47135                         alert('No items in the toolbar');
47136                     }
47137                 }
47138             },
47139             {
47140                 text   : 'Remove all items',
47141                 scope  : this,
47142                 handler: function() {
47143                     toolbar.removeAll();
47144                 }
47145             }
47146         ]
47147     });
47148
47149  * @constructor
47150  * Creates a new Toolbar
47151  * @param {Object/Array} config A config object or an array of buttons to <code>{@link #add}</code>
47152  * @xtype toolbar
47153  * @docauthor Robert Dougan <rob@sencha.com>
47154  * @markdown
47155  */
47156 Ext.define('Ext.toolbar.Toolbar', {
47157     extend: 'Ext.container.Container',
47158     requires: [
47159         'Ext.toolbar.Fill',
47160         'Ext.layout.container.HBox',
47161         'Ext.layout.container.VBox',
47162         'Ext.FocusManager'
47163     ],
47164     uses: [
47165         'Ext.toolbar.Separator'
47166     ],
47167     alias: 'widget.toolbar',
47168     alternateClassName: 'Ext.Toolbar',
47169     
47170     isToolbar: true,
47171     baseCls  : Ext.baseCSSPrefix + 'toolbar',
47172     ariaRole : 'toolbar',
47173     
47174     defaultType: 'button',
47175     
47176     /**
47177      * @cfg {Boolean} vertical
47178      * Set to `true` to make the toolbar vertical. The layout will become a `vbox`.
47179      * (defaults to `false`)
47180      */
47181     vertical: false,
47182
47183     /**
47184      * @cfg {String/Object} layout
47185      * This class assigns a default layout (<code>layout:'<b>hbox</b>'</code>).
47186      * Developers <i>may</i> override this configuration option if another layout
47187      * is required (the constructor must be passed a configuration object in this
47188      * case instead of an array).
47189      * See {@link Ext.container.Container#layout} for additional information.
47190      */
47191
47192     /**
47193      * @cfg {Boolean} enableOverflow
47194      * Defaults to false. Configure <code>true</code> to make the toolbar provide a button
47195      * which activates a dropdown Menu to show items which overflow the Toolbar's width.
47196      */
47197     enableOverflow: false,
47198     
47199     // private
47200     trackMenus: true,
47201     
47202     itemCls: Ext.baseCSSPrefix + 'toolbar-item',
47203     
47204     initComponent: function() {
47205         var me = this,
47206             keys;
47207
47208         // check for simplified (old-style) overflow config:
47209         if (!me.layout && me.enableOverflow) {
47210             me.layout = { overflowHandler: 'Menu' };
47211         }
47212         
47213         if (me.dock === 'right' || me.dock === 'left') {
47214             me.vertical = true;
47215         }
47216
47217         me.layout = Ext.applyIf(Ext.isString(me.layout) ? {
47218             type: me.layout
47219         } : me.layout || {}, {
47220             type: me.vertical ? 'vbox' : 'hbox',
47221             align: me.vertical ? 'stretchmax' : 'middle'
47222         });
47223         
47224         if (me.vertical) {
47225             me.addClsWithUI('vertical');
47226         }
47227         
47228         // @TODO: remove this hack and implement a more general solution
47229         if (me.ui === 'footer') {
47230             me.ignoreBorderManagement = true;
47231         }
47232         
47233         me.callParent();
47234
47235         /**
47236          * @event overflowchange
47237          * Fires after the overflow state has changed.
47238          * @param {Object} c The Container
47239          * @param {Boolean} lastOverflow overflow state
47240          */
47241         me.addEvents('overflowchange');
47242         
47243         // Subscribe to Ext.FocusManager for key navigation
47244         keys = me.vertical ? ['up', 'down'] : ['left', 'right'];
47245         Ext.FocusManager.subscribe(me, {
47246             keys: keys
47247         });
47248     },
47249
47250     /**
47251      * <p>Adds element(s) to the toolbar -- this function takes a variable number of
47252      * arguments of mixed type and adds them to the toolbar.</p>
47253      * <br><p><b>Note</b>: See the notes within {@link Ext.container.Container#add}.</p>
47254      * @param {Mixed} arg1 The following types of arguments are all valid:<br />
47255      * <ul>
47256      * <li>{@link Ext.button.Button} config: A valid button config object (equivalent to {@link #addButton})</li>
47257      * <li>HtmlElement: Any standard HTML element (equivalent to {@link #addElement})</li>
47258      * <li>Field: Any form field (equivalent to {@link #addField})</li>
47259      * <li>Item: Any subclass of {@link Ext.toolbar.Item} (equivalent to {@link #addItem})</li>
47260      * <li>String: Any generic string (gets wrapped in a {@link Ext.toolbar.TextItem}, equivalent to {@link #addText}).
47261      * Note that there are a few special strings that are treated differently as explained next.</li>
47262      * <li>'-': Creates a separator element (equivalent to {@link #addSeparator})</li>
47263      * <li>' ': Creates a spacer element (equivalent to {@link #addSpacer})</li>
47264      * <li>'->': Creates a fill element (equivalent to {@link #addFill})</li>
47265      * </ul>
47266      * @param {Mixed} arg2
47267      * @param {Mixed} etc.
47268      * @method add
47269      */
47270
47271     // private
47272     lookupComponent: function(c) {
47273         if (Ext.isString(c)) {
47274             var shortcut = Ext.toolbar.Toolbar.shortcuts[c];
47275             if (shortcut) {
47276                 c = {
47277                     xtype: shortcut
47278                 };
47279             } else {
47280                 c = {
47281                     xtype: 'tbtext',
47282                     text: c
47283                 };
47284             }
47285             this.applyDefaults(c);
47286         }
47287         return this.callParent(arguments);
47288     },
47289
47290     // private
47291     applyDefaults: function(c) {
47292         if (!Ext.isString(c)) {
47293             c = this.callParent(arguments);
47294             var d = this.internalDefaults;
47295             if (c.events) {
47296                 Ext.applyIf(c.initialConfig, d);
47297                 Ext.apply(c, d);
47298             } else {
47299                 Ext.applyIf(c, d);
47300             }
47301         }
47302         return c;
47303     },
47304
47305     // private
47306     trackMenu: function(item, remove) {
47307         if (this.trackMenus && item.menu) {
47308             var method = remove ? 'mun' : 'mon',
47309                 me = this;
47310
47311             me[method](item, 'menutriggerover', me.onButtonTriggerOver, me);
47312             me[method](item, 'menushow', me.onButtonMenuShow, me);
47313             me[method](item, 'menuhide', me.onButtonMenuHide, me);
47314         }
47315     },
47316
47317     // private
47318     constructButton: function(item) {
47319         return item.events ? item : this.createComponent(item, item.split ? 'splitbutton' : this.defaultType);
47320     },
47321
47322     // private
47323     onBeforeAdd: function(component) {
47324         if (component.is('field') || (component.is('button') && this.ui != 'footer')) {
47325             component.ui = component.ui + '-toolbar';
47326         }
47327         
47328         // Any separators needs to know if is vertical or not
47329         if (component instanceof Ext.toolbar.Separator) {
47330             component.setUI((this.vertical) ? 'vertical' : 'horizontal');
47331         }
47332         
47333         this.callParent(arguments);
47334     },
47335
47336     // private
47337     onAdd: function(component) {
47338         this.callParent(arguments);
47339
47340         this.trackMenu(component);
47341         if (this.disabled) {
47342             component.disable();
47343         }
47344     },
47345
47346     // private
47347     onRemove: function(c) {
47348         this.callParent(arguments);
47349         this.trackMenu(c, true);
47350     },
47351
47352     // private
47353     onButtonTriggerOver: function(btn){
47354         if (this.activeMenuBtn && this.activeMenuBtn != btn) {
47355             this.activeMenuBtn.hideMenu();
47356             btn.showMenu();
47357             this.activeMenuBtn = btn;
47358         }
47359     },
47360
47361     // private
47362     onButtonMenuShow: function(btn) {
47363         this.activeMenuBtn = btn;
47364     },
47365
47366     // private
47367     onButtonMenuHide: function(btn) {
47368         delete this.activeMenuBtn;
47369     }
47370 }, function() {
47371     this.shortcuts = {
47372         '-' : 'tbseparator',
47373         ' ' : 'tbspacer',
47374         '->': 'tbfill'
47375     };
47376 });
47377 /**
47378  * @class Ext.panel.AbstractPanel
47379  * @extends Ext.container.Container
47380  * <p>A base class which provides methods common to Panel classes across the Sencha product range.</p>
47381  * <p>Please refer to sub class's documentation</p>
47382  * @constructor
47383  * @param {Object} config The config object
47384  */
47385 Ext.define('Ext.panel.AbstractPanel', {
47386
47387     /* Begin Definitions */
47388
47389     extend: 'Ext.container.Container',
47390
47391     requires: ['Ext.util.MixedCollection', 'Ext.core.Element', 'Ext.toolbar.Toolbar'],
47392
47393     /* End Definitions */
47394
47395     /**
47396      * @cfg {String} baseCls
47397      * The base CSS class to apply to this panel's element (defaults to <code>'x-panel'</code>).
47398      */
47399     baseCls : Ext.baseCSSPrefix + 'panel',
47400
47401     /**
47402      * @cfg {Number/String} bodyPadding
47403      * A shortcut for setting a padding style on the body element. The value can either be
47404      * a number to be applied to all sides, or a normal css string describing padding.
47405      * Defaults to <code>undefined</code>.
47406      */
47407
47408     /**
47409      * @cfg {Boolean} bodyBorder
47410      * 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`.
47411      * Defaults to <code>undefined</code>.
47412      */
47413     
47414     /**
47415      * @cfg {String/Object/Function} bodyStyle
47416      * Custom CSS styles to be applied to the panel's body element, which can be supplied as a valid CSS style string,
47417      * an object containing style property name/value pairs or a function that returns such a string or object.
47418      * For example, these two formats are interpreted to be equivalent:<pre><code>
47419 bodyStyle: 'background:#ffc; padding:10px;'
47420
47421 bodyStyle: {
47422     background: '#ffc',
47423     padding: '10px'
47424 }
47425      * </code></pre>
47426      */
47427     
47428     /**
47429      * @cfg {String/Array} bodyCls
47430      * A CSS class, space-delimited string of classes, or array of classes to be applied to the panel's body element.
47431      * The following examples are all valid:<pre><code>
47432 bodyCls: 'foo'
47433 bodyCls: 'foo bar'
47434 bodyCls: ['foo', 'bar']
47435      * </code></pre>
47436      */
47437
47438     isPanel: true,
47439
47440     componentLayout: 'dock',
47441
47442     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>'],
47443
47444     // TODO: Move code examples into product-specific files. The code snippet below is Touch only.
47445     /**
47446      * @cfg {Object/Array} dockedItems
47447      * A component or series of components to be added as docked items to this panel.
47448      * The docked items can be docked to either the top, right, left or bottom of a panel.
47449      * This is typically used for things like toolbars or tab bars:
47450      * <pre><code>
47451 var panel = new Ext.panel.Panel({
47452     fullscreen: true,
47453     dockedItems: [{
47454         xtype: 'toolbar',
47455         dock: 'top',
47456         items: [{
47457             text: 'Docked to the top'
47458         }]
47459     }]
47460 });</code></pre>
47461      */
47462      
47463     border: true,
47464
47465     initComponent : function() {
47466         var me = this;
47467         
47468         me.addEvents(
47469             /**
47470              * @event bodyresize
47471              * Fires after the Panel has been resized.
47472              * @param {Ext.panel.Panel} p the Panel which has been resized.
47473              * @param {Number} width The Panel body's new width.
47474              * @param {Number} height The Panel body's new height.
47475              */
47476             'bodyresize'
47477             // // inherited
47478             // 'activate',
47479             // // inherited
47480             // 'deactivate'
47481         );
47482
47483         Ext.applyIf(me.renderSelectors, {
47484             body: '.' + me.baseCls + '-body'
47485         });
47486         
47487         //!frame 
47488         //!border
47489         
47490         if (me.frame && me.border && me.bodyBorder === undefined) {
47491             me.bodyBorder = false;
47492         }
47493         if (me.frame && me.border && (me.bodyBorder === false || me.bodyBorder === 0)) {
47494             me.manageBodyBorders = true;
47495         }
47496         
47497         me.callParent();
47498     },
47499
47500     // @private
47501     initItems : function() {
47502         var me = this,
47503             items = me.dockedItems;
47504             
47505         me.callParent();
47506         me.dockedItems = Ext.create('Ext.util.MixedCollection', false, me.getComponentId);
47507         if (items) {
47508             me.addDocked(items);
47509         }
47510     },
47511
47512     /**
47513      * Finds a docked component by id, itemId or position. Also see {@link #getDockedItems}
47514      * @param {String/Number} comp The id, itemId or position of the docked component (see {@link #getComponent} for details)
47515      * @return {Ext.Component} The docked component (if found)
47516      */
47517     getDockedComponent: function(comp) {
47518         if (Ext.isObject(comp)) {
47519             comp = comp.getItemId();
47520         }
47521         return this.dockedItems.get(comp);
47522     },
47523
47524     /**
47525      * Attempts a default component lookup (see {@link Ext.container.Container#getComponent}). If the component is not found in the normal
47526      * items, the dockedItems are searched and the matched component (if any) returned (see {@loink #getDockedComponent}). Note that docked
47527      * items will only be matched by component id or itemId -- if you pass a numeric index only non-docked child components will be searched.
47528      * @param {String/Number} comp The component id, itemId or position to find
47529      * @return {Ext.Component} The component (if found)
47530      */
47531     getComponent: function(comp) {
47532         var component = this.callParent(arguments);
47533         if (component === undefined && !Ext.isNumber(comp)) {
47534             // If the arg is a numeric index skip docked items
47535             component = this.getDockedComponent(comp);
47536         }
47537         return component;
47538     },
47539
47540     /**
47541      * Parses the {@link bodyStyle} config if available to create a style string that will be applied to the body element.
47542      * This also includes {@link bodyPadding} and {@link bodyBorder} if available.
47543      * @return {String} A CSS style string with body styles, padding and border.
47544      * @private
47545      */
47546     initBodyStyles: function() {
47547         var me = this,
47548             bodyStyle = me.bodyStyle,
47549             styles = [],
47550             Element = Ext.core.Element,
47551             prop;
47552
47553         if (Ext.isFunction(bodyStyle)) {
47554             bodyStyle = bodyStyle();
47555         }
47556         if (Ext.isString(bodyStyle)) {
47557             styles = bodyStyle.split(';');
47558         } else {
47559             for (prop in bodyStyle) {
47560                 if (bodyStyle.hasOwnProperty(prop)) {
47561                     styles.push(prop + ':' + bodyStyle[prop]);
47562                 }
47563             }
47564         }
47565
47566         if (me.bodyPadding !== undefined) {
47567             styles.push('padding: ' + Element.unitizeBox((me.bodyPadding === true) ? 5 : me.bodyPadding));
47568         }
47569         if (me.frame && me.bodyBorder) {
47570             if (!Ext.isNumber(me.bodyBorder)) {
47571                 me.bodyBorder = 1;
47572             }
47573             styles.push('border-width: ' + Element.unitizeBox(me.bodyBorder));
47574         }
47575         delete me.bodyStyle;
47576         return styles.length ? styles.join(';') : undefined;
47577     },
47578     
47579     /**
47580      * Parse the {@link bodyCls} config if available to create a comma-delimited string of 
47581      * CSS classes to be applied to the body element.
47582      * @return {String} The CSS class(es)
47583      * @private
47584      */
47585     initBodyCls: function() {
47586         var me = this,
47587             cls = '',
47588             bodyCls = me.bodyCls;
47589         
47590         if (bodyCls) {
47591             Ext.each(bodyCls, function(v) {
47592                 cls += " " + v;
47593             });
47594             delete me.bodyCls;
47595         }
47596         return cls.length > 0 ? cls : undefined;
47597     },
47598     
47599     /**
47600      * Initialized the renderData to be used when rendering the renderTpl.
47601      * @return {Object} Object with keys and values that are going to be applied to the renderTpl
47602      * @private
47603      */
47604     initRenderData: function() {
47605         return Ext.applyIf(this.callParent(), {
47606             bodyStyle: this.initBodyStyles(),
47607             bodyCls: this.initBodyCls()
47608         });
47609     },
47610
47611     /**
47612      * Adds docked item(s) to the panel.
47613      * @param {Object/Array} component The Component or array of components to add. The components
47614      * must include a 'dock' parameter on each component to indicate where it should be docked ('top', 'right',
47615      * 'bottom', 'left').
47616      * @param {Number} pos (optional) The index at which the Component will be added
47617      */
47618     addDocked : function(items, pos) {
47619         var me = this,
47620             i = 0,
47621             item, length;
47622
47623         items = me.prepareItems(items);
47624         length = items.length;
47625
47626         for (; i < length; i++) {
47627             item = items[i];
47628             item.dock = item.dock || 'top';
47629
47630             // Allow older browsers to target docked items to style without borders
47631             if (me.border === false) {
47632                 // item.cls = item.cls || '' + ' ' + me.baseCls + '-noborder-docked-' + item.dock;
47633             }
47634
47635             if (pos !== undefined) {
47636                 me.dockedItems.insert(pos + i, item);
47637             }
47638             else {
47639                 me.dockedItems.add(item);
47640             }
47641             item.onAdded(me, i);
47642             me.onDockedAdd(item);
47643         }
47644         if (me.rendered) {
47645             me.doComponentLayout();
47646         }
47647         return items;
47648     },
47649
47650     // Placeholder empty functions
47651     onDockedAdd : Ext.emptyFn,
47652     onDockedRemove : Ext.emptyFn,
47653
47654     /**
47655      * Inserts docked item(s) to the panel at the indicated position.
47656      * @param {Number} pos The index at which the Component will be inserted
47657      * @param {Object/Array} component. The Component or array of components to add. The components
47658      * must include a 'dock' paramater on each component to indicate where it should be docked ('top', 'right',
47659      * 'bottom', 'left').
47660      */
47661     insertDocked : function(pos, items) {
47662         this.addDocked(items, pos);
47663     },
47664
47665     /**
47666      * Removes the docked item from the panel.
47667      * @param {Ext.Component} item. The Component to remove.
47668      * @param {Boolean} autoDestroy (optional) Destroy the component after removal.
47669      */
47670     removeDocked : function(item, autoDestroy) {
47671         var me = this,
47672             layout,
47673             hasLayout;
47674             
47675         if (!me.dockedItems.contains(item)) {
47676             return item;
47677         }
47678
47679         layout = me.componentLayout;
47680         hasLayout = layout && me.rendered;
47681
47682         if (hasLayout) {
47683             layout.onRemove(item);
47684         }
47685
47686         me.dockedItems.remove(item);
47687         item.onRemoved();
47688         me.onDockedRemove(item);
47689
47690         if (autoDestroy === true || (autoDestroy !== false && me.autoDestroy)) {
47691             item.destroy();
47692         }
47693
47694         if (hasLayout && !autoDestroy) {
47695             layout.afterRemove(item);
47696         }
47697         
47698         if (!this.destroying) {
47699             me.doComponentLayout();
47700         }
47701
47702         return item;
47703     },
47704
47705     /**
47706      * Retrieve an array of all currently docked Components.
47707      * @param {String} cqSelector A {@link Ext.ComponentQuery ComponentQuery} selector string to filter the returned items.
47708      * @return {Array} An array of components.
47709      */
47710     getDockedItems : function(cqSelector) {
47711         var me = this,
47712             // Start with a weight of 1, so users can provide <= 0 to come before top items
47713             // Odd numbers, so users can provide a weight to come in between if desired
47714             defaultWeight = { top: 1, left: 3, right: 5, bottom: 7 },
47715             dockedItems;
47716
47717         if (me.dockedItems && me.dockedItems.items.length) {
47718             // Allow filtering of returned docked items by CQ selector.
47719             if (cqSelector) {
47720                 dockedItems = Ext.ComponentQuery.query(cqSelector, me.dockedItems.items);
47721             } else {
47722                 dockedItems = me.dockedItems.items.slice();
47723             }
47724
47725             Ext.Array.sort(dockedItems, function(a, b) {
47726                 // Docked items are ordered by their visual representation by default (t,l,r,b)
47727                 // TODO: Enforce position ordering, and have weights be sub-ordering within positions?
47728                 var aw = a.weight || defaultWeight[a.dock],
47729                     bw = b.weight || defaultWeight[b.dock];
47730                 if (Ext.isNumber(aw) && Ext.isNumber(bw)) {
47731                     return aw - bw;
47732                 }
47733                 return 0;
47734             });
47735             
47736             return dockedItems;
47737         }
47738         return [];
47739     },
47740     
47741     // inherit docs
47742     addUIClsToElement: function(cls, force) {
47743         var me = this;
47744         
47745         me.callParent(arguments);
47746         
47747         if (!force && me.rendered) {
47748             me.body.addCls(Ext.baseCSSPrefix + cls);
47749             me.body.addCls(me.baseCls + '-body-' + cls);
47750             me.body.addCls(me.baseCls + '-body-' + me.ui + '-' + cls);
47751         }
47752     },
47753     
47754     // inherit docs
47755     removeUIClsFromElement: function(cls, force) {
47756         var me = this;
47757         
47758         me.callParent(arguments);
47759         
47760         if (!force && me.rendered) {
47761             me.body.removeCls(Ext.baseCSSPrefix + cls);
47762             me.body.removeCls(me.baseCls + '-body-' + cls);
47763             me.body.removeCls(me.baseCls + '-body-' + me.ui + '-' + cls);
47764         }
47765     },
47766     
47767     // inherit docs
47768     addUIToElement: function(force) {
47769         var me = this;
47770         
47771         me.callParent(arguments);
47772         
47773         if (!force && me.rendered) {
47774             me.body.addCls(me.baseCls + '-body-' + me.ui);
47775         }
47776     },
47777     
47778     // inherit docs
47779     removeUIFromElement: function() {
47780         var me = this;
47781         
47782         me.callParent(arguments);
47783         
47784         if (me.rendered) {
47785             me.body.removeCls(me.baseCls + '-body-' + me.ui);
47786         }
47787     },
47788
47789     // @private
47790     getTargetEl : function() {
47791         return this.body;
47792     },
47793
47794     getRefItems: function(deep) {
47795         var items = this.callParent(arguments),
47796             // deep fetches all docked items, and their descendants using '*' selector and then '* *'
47797             dockedItems = this.getDockedItems(deep ? '*,* *' : undefined),
47798             ln = dockedItems.length,
47799             i = 0,
47800             item;
47801         
47802         // Find the index where we go from top/left docked items to right/bottom docked items
47803         for (; i < ln; i++) {
47804             item = dockedItems[i];
47805             if (item.dock === 'right' || item.dock === 'bottom') {
47806                 break;
47807             }
47808         }
47809         
47810         // Return docked items in the top/left position before our container items, and
47811         // return right/bottom positioned items after our container items.
47812         // See AbstractDock.renderItems() for more information.
47813         return dockedItems.splice(0, i).concat(items).concat(dockedItems);
47814     },
47815
47816     beforeDestroy: function(){
47817         var docked = this.dockedItems,
47818             c;
47819
47820         if (docked) {
47821             while ((c = docked.first())) {
47822                 this.removeDocked(c, true);
47823             }
47824         }
47825         this.callParent();
47826     },
47827     
47828     setBorder: function(border) {
47829         var me = this;
47830         me.border = (border !== undefined) ? border : true;
47831         if (me.rendered) {
47832             me.doComponentLayout();
47833         }
47834     }
47835 });
47836 /**
47837  * @class Ext.panel.Header
47838  * @extends Ext.container.Container
47839  * Simple header class which is used for on {@link Ext.panel.Panel} and {@link Ext.window.Window}
47840  * @xtype header
47841  */
47842 Ext.define('Ext.panel.Header', {
47843     extend: 'Ext.container.Container',
47844     uses: ['Ext.panel.Tool', 'Ext.draw.Component', 'Ext.util.CSS'],
47845     alias: 'widget.header',
47846
47847     isHeader       : true,
47848     defaultType    : 'tool',
47849     indicateDrag   : false,
47850     weight         : -1,
47851
47852     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>'],
47853
47854     initComponent: function() {
47855         var me = this,
47856             rule,
47857             style,
47858             titleTextEl,
47859             ui;
47860
47861         me.indicateDragCls = me.baseCls + '-draggable';
47862         me.title = me.title || '&#160;';
47863         me.tools = me.tools || [];
47864         me.items = me.items || [];
47865         me.orientation = me.orientation || 'horizontal';
47866         me.dock = (me.dock) ? me.dock : (me.orientation == 'horizontal') ? 'top' : 'left';
47867
47868         //add the dock as a ui
47869         //this is so we support top/right/left/bottom headers
47870         me.addClsWithUI(me.orientation);
47871         me.addClsWithUI(me.dock);
47872
47873         Ext.applyIf(me.renderSelectors, {
47874             body: '.' + me.baseCls + '-body'
47875         });
47876
47877         // Add Icon
47878         if (!Ext.isEmpty(me.iconCls)) {
47879             me.initIconCmp();
47880             me.items.push(me.iconCmp);
47881         }
47882
47883         // Add Title
47884         if (me.orientation == 'vertical') {
47885             // Hack for IE6/7's inability to display an inline-block
47886             if (Ext.isIE6 || Ext.isIE7) {
47887                 me.width = this.width || 24;
47888             } else if (Ext.isIEQuirks) {
47889                 me.width = this.width || 25;
47890             }
47891
47892             me.layout = {
47893                 type : 'vbox',
47894                 align: 'center',
47895                 clearInnerCtOnLayout: true,
47896                 bindToOwnerCtContainer: false
47897             };
47898             me.textConfig = {
47899                 cls: me.baseCls + '-text',
47900                 type: 'text',
47901                 text: me.title,
47902                 rotate: {
47903                     degrees: 90
47904                 }
47905             };
47906             ui = me.ui;
47907             if (Ext.isArray(ui)) {
47908                 ui = ui[0];
47909             }
47910             rule = Ext.util.CSS.getRule('.' + me.baseCls + '-text-' + ui);
47911             if (rule) {
47912                 style = rule.style;
47913             }
47914             if (style) {
47915                 Ext.apply(me.textConfig, {
47916                     'font-family': style.fontFamily,
47917                     'font-weight': style.fontWeight,
47918                     'font-size': style.fontSize,
47919                     fill: style.color
47920                 });
47921             }
47922             me.titleCmp = Ext.create('Ext.draw.Component', {
47923                 ariaRole  : 'heading',
47924                 focusable: false,
47925                 viewBox: false,
47926                 autoSize: true,
47927                 margins: '5 0 0 0',
47928                 items: [ me.textConfig ],
47929                 renderSelectors: {
47930                     textEl: '.' + me.baseCls + '-text'
47931                 }
47932             });
47933         } else {
47934             me.layout = {
47935                 type : 'hbox',
47936                 align: 'middle',
47937                 clearInnerCtOnLayout: true,
47938                 bindToOwnerCtContainer: false
47939             };
47940             me.titleCmp = Ext.create('Ext.Component', {
47941                 xtype     : 'component',
47942                 ariaRole  : 'heading',
47943                 focusable: false,
47944                 renderTpl : ['<span class="{cls}-text {cls}-text-{ui}">{title}</span>'],
47945                 renderData: {
47946                     title: me.title,
47947                     cls  : me.baseCls,
47948                     ui   : me.ui
47949                 },
47950                 renderSelectors: {
47951                     textEl: '.' + me.baseCls + '-text'
47952                 }
47953             });
47954         }
47955         me.items.push(me.titleCmp);
47956
47957         // Spacer ->
47958         me.items.push({
47959             xtype: 'component',
47960             html : '&nbsp;',
47961             flex : 1,
47962             focusable: false
47963         });
47964
47965         // Add Tools
47966         me.items = me.items.concat(me.tools);
47967         this.callParent();
47968     },
47969
47970     initIconCmp: function() {
47971         this.iconCmp = Ext.create('Ext.Component', {
47972             focusable: false,
47973             renderTpl : ['<img alt="" src="{blank}" class="{cls}-icon {iconCls}"/>'],
47974             renderData: {
47975                 blank  : Ext.BLANK_IMAGE_URL,
47976                 cls    : this.baseCls,
47977                 iconCls: this.iconCls,
47978                 orientation: this.orientation
47979             },
47980             renderSelectors: {
47981                 iconEl: '.' + this.baseCls + '-icon'
47982             },
47983             iconCls: this.iconCls
47984         });
47985     },
47986
47987     afterRender: function() {
47988         var me = this;
47989
47990         me.el.unselectable();
47991         if (me.indicateDrag) {
47992             me.el.addCls(me.indicateDragCls);
47993         }
47994         me.mon(me.el, {
47995             click: me.onClick,
47996             scope: me
47997         });
47998         me.callParent();
47999     },
48000
48001     afterLayout: function() {
48002         var me = this;
48003         me.callParent(arguments);
48004
48005         // IE7 needs a forced repaint to make the top framing div expand to full width
48006         if (Ext.isIE7) {
48007             me.el.repaint();
48008         }
48009     },
48010
48011     // inherit docs
48012     addUIClsToElement: function(cls, force) {
48013         var me = this;
48014
48015         me.callParent(arguments);
48016
48017         if (!force && me.rendered) {
48018             me.body.addCls(me.baseCls + '-body-' + cls);
48019             me.body.addCls(me.baseCls + '-body-' + me.ui + '-' + cls);
48020         }
48021     },
48022
48023     // inherit docs
48024     removeUIClsFromElement: function(cls, force) {
48025         var me = this;
48026
48027         me.callParent(arguments);
48028
48029         if (!force && me.rendered) {
48030             me.body.removeCls(me.baseCls + '-body-' + cls);
48031             me.body.removeCls(me.baseCls + '-body-' + me.ui + '-' + cls);
48032         }
48033     },
48034
48035     // inherit docs
48036     addUIToElement: function(force) {
48037         var me = this;
48038
48039         me.callParent(arguments);
48040
48041         if (!force && me.rendered) {
48042             me.body.addCls(me.baseCls + '-body-' + me.ui);
48043         }
48044
48045         if (!force && me.titleCmp && me.titleCmp.rendered && me.titleCmp.textEl) {
48046             me.titleCmp.textEl.addCls(me.baseCls + '-text-' + me.ui);
48047         }
48048     },
48049
48050     // inherit docs
48051     removeUIFromElement: function() {
48052         var me = this;
48053
48054         me.callParent(arguments);
48055
48056         if (me.rendered) {
48057             me.body.removeCls(me.baseCls + '-body-' + me.ui);
48058         }
48059
48060         if (me.titleCmp && me.titleCmp.rendered && me.titleCmp.textEl) {
48061             me.titleCmp.textEl.removeCls(me.baseCls + '-text-' + me.ui);
48062         }
48063     },
48064
48065     onClick: function(e) {
48066         if (!e.getTarget(Ext.baseCSSPrefix + 'tool')) {
48067             this.fireEvent('click', e);
48068         }
48069     },
48070
48071     getTargetEl: function() {
48072         return this.body || this.frameBody || this.el;
48073     },
48074
48075     /**
48076      * Sets the title of the header.
48077      * @param {String} title The title to be set
48078      */
48079     setTitle: function(title) {
48080         var me = this;
48081         if (me.rendered) {
48082             if (me.titleCmp.rendered) {
48083                 if (me.titleCmp.surface) {
48084                     me.title = title || '';
48085                     var sprite = me.titleCmp.surface.items.items[0],
48086                         surface = me.titleCmp.surface;
48087
48088                     surface.remove(sprite);
48089                     me.textConfig.type = 'text';
48090                     me.textConfig.text = title;
48091                     sprite = surface.add(me.textConfig);
48092                     sprite.setAttributes({
48093                         rotate: {
48094                             degrees: 90
48095                         }
48096                     }, true);
48097                     me.titleCmp.autoSizeSurface();
48098                 } else {
48099                     me.title = title || '&#160;';
48100                     me.titleCmp.textEl.update(me.title);
48101                 }
48102             } else {
48103                 me.titleCmp.on({
48104                     render: function() {
48105                         me.setTitle(title);
48106                     },
48107                     single: true
48108                 });
48109             }
48110         } else {
48111             me.on({
48112                 render: function() {
48113                     me.layout.layout();
48114                     me.setTitle(title);
48115                 },
48116                 single: true
48117             });
48118         }
48119     },
48120
48121     /**
48122      * Sets the CSS class that provides the icon image for this panel.  This method will replace any existing
48123      * icon class if one has already been set and fire the {@link #iconchange} event after completion.
48124      * @param {String} cls The new CSS class name
48125      */
48126     setIconCls: function(cls) {
48127         this.iconCls = cls;
48128         if (!this.iconCmp) {
48129             this.initIconCmp();
48130             this.insert(0, this.iconCmp);
48131         }
48132         else {
48133             if (!cls || !cls.length) {
48134                 this.iconCmp.destroy();
48135             }
48136             else {
48137                 var iconCmp = this.iconCmp,
48138                     el      = iconCmp.iconEl;
48139
48140                 el.removeCls(iconCmp.iconCls);
48141                 el.addCls(cls);
48142                 iconCmp.iconCls = cls;
48143             }
48144         }
48145     },
48146
48147     /**
48148      * Add a tool to the header
48149      * @param {Object} tool
48150      */
48151     addTool: function(tool) {
48152         this.tools.push(this.add(tool));
48153     },
48154
48155     /**
48156      * @private
48157      * Set up the tools.&lt;tool type> link in the owning Panel.
48158      * Bind the tool to its owning Panel.
48159      * @param component
48160      * @param index
48161      */
48162     onAdd: function(component, index) {
48163         this.callParent([arguments]);
48164         if (component instanceof Ext.panel.Tool) {
48165             component.bindTo(this.ownerCt);
48166             this.tools[component.type] = component;
48167         }
48168     }
48169 });
48170
48171 /**
48172  * @class Ext.fx.target.Element
48173  * @extends Ext.fx.target.Target
48174  * 
48175  * This class represents a animation target for an {@link Ext.core.Element}. In general this class will not be
48176  * created directly, the {@link Ext.core.Element} will be passed to the animation and
48177  * and the appropriate target will be created.
48178  */
48179 Ext.define('Ext.fx.target.Element', {
48180
48181     /* Begin Definitions */
48182     
48183     extend: 'Ext.fx.target.Target',
48184     
48185     /* End Definitions */
48186
48187     type: 'element',
48188
48189     getElVal: function(el, attr, val) {
48190         if (val == undefined) {
48191             if (attr === 'x') {
48192                 val = el.getX();
48193             }
48194             else if (attr === 'y') {
48195                 val = el.getY();
48196             }
48197             else if (attr === 'scrollTop') {
48198                 val = el.getScroll().top;
48199             }
48200             else if (attr === 'scrollLeft') {
48201                 val = el.getScroll().left;
48202             }
48203             else if (attr === 'height') {
48204                 val = el.getHeight();
48205             }
48206             else if (attr === 'width') {
48207                 val = el.getWidth();
48208             }
48209             else {
48210                 val = el.getStyle(attr);
48211             }
48212         }
48213         return val;
48214     },
48215
48216     getAttr: function(attr, val) {
48217         var el = this.target;
48218         return [[ el, this.getElVal(el, attr, val)]];
48219     },
48220
48221     setAttr: function(targetData) {
48222         var target = this.target,
48223             ln = targetData.length,
48224             attrs, attr, o, i, j, ln2, element, value;
48225         for (i = 0; i < ln; i++) {
48226             attrs = targetData[i].attrs;
48227             for (attr in attrs) {
48228                 if (attrs.hasOwnProperty(attr)) {
48229                     ln2 = attrs[attr].length;
48230                     for (j = 0; j < ln2; j++) {
48231                         o = attrs[attr][j];
48232                         element = o[0];
48233                         value = o[1];
48234                         if (attr === 'x') {
48235                             element.setX(value);
48236                         }
48237                         else if (attr === 'y') {
48238                             element.setY(value);
48239                         }
48240                         else if (attr === 'scrollTop') {
48241                             element.scrollTo('top', value);
48242                         }
48243                         else if (attr === 'scrollLeft') {
48244                             element.scrollTo('left',value);
48245                         }
48246                         else {
48247                             element.setStyle(attr, value);
48248                         }
48249                     }
48250                 }
48251             }
48252         }
48253     }
48254 });
48255
48256 /**
48257  * @class Ext.fx.target.CompositeElement
48258  * @extends Ext.fx.target.Element
48259  * 
48260  * This class represents a animation target for a {@link Ext.CompositeElement}. It allows
48261  * each {@link Ext.core.Element} in the group to be animated as a whole. In general this class will not be
48262  * created directly, the {@link Ext.CompositeElement} will be passed to the animation and
48263  * and the appropriate target will be created.
48264  */
48265 Ext.define('Ext.fx.target.CompositeElement', {
48266
48267     /* Begin Definitions */
48268
48269     extend: 'Ext.fx.target.Element',
48270
48271     /* End Definitions */
48272
48273     isComposite: true,
48274     
48275     constructor: function(target) {
48276         target.id = target.id || Ext.id(null, 'ext-composite-');
48277         this.callParent([target]);
48278     },
48279
48280     getAttr: function(attr, val) {
48281         var out = [],
48282             target = this.target;
48283         target.each(function(el) {
48284             out.push([el, this.getElVal(el, attr, val)]);
48285         }, this);
48286         return out;
48287     }
48288 });
48289
48290 /**
48291  * @class Ext.fx.Manager
48292  * Animation Manager which keeps track of all current animations and manages them on a frame by frame basis.
48293  * @private
48294  * @singleton
48295  */
48296
48297 Ext.define('Ext.fx.Manager', {
48298
48299     /* Begin Definitions */
48300
48301     singleton: true,
48302
48303     requires: ['Ext.util.MixedCollection',
48304                'Ext.fx.target.Element',
48305                'Ext.fx.target.CompositeElement',
48306                'Ext.fx.target.Sprite',
48307                'Ext.fx.target.CompositeSprite',
48308                'Ext.fx.target.Component'],
48309
48310     mixins: {
48311         queue: 'Ext.fx.Queue'
48312     },
48313
48314     /* End Definitions */
48315
48316     constructor: function() {
48317         this.items = Ext.create('Ext.util.MixedCollection');
48318         this.mixins.queue.constructor.call(this);
48319
48320         // this.requestAnimFrame = (function() {
48321         //     var raf = window.requestAnimationFrame ||
48322         //               window.webkitRequestAnimationFrame ||
48323         //               window.mozRequestAnimationFrame ||
48324         //               window.oRequestAnimationFrame ||
48325         //               window.msRequestAnimationFrame;
48326         //     if (raf) {
48327         //         return function(callback, element) {
48328         //             raf(callback);
48329         //         };
48330         //     }
48331         //     else {
48332         //         return function(callback, element) {
48333         //             window.setTimeout(callback, Ext.fx.Manager.interval);
48334         //         };
48335         //     }
48336         // })();
48337     },
48338
48339     /**
48340      * @cfg {Number} interval Default interval in miliseconds to calculate each frame.  Defaults to 16ms (~60fps)
48341      */
48342     interval: 16,
48343
48344     /**
48345      * @cfg {Boolean} forceJS Turn off to not use CSS3 transitions when they are available
48346      */
48347     forceJS: true,
48348
48349     // @private Target factory
48350     createTarget: function(target) {
48351         var me = this,
48352             useCSS3 = !me.forceJS && Ext.supports.Transitions,
48353             targetObj;
48354
48355         me.useCSS3 = useCSS3;
48356
48357         // dom id
48358         if (Ext.isString(target)) {
48359             target = Ext.get(target);
48360         }
48361         // dom element
48362         if (target && target.tagName) {
48363             target = Ext.get(target);
48364             targetObj = Ext.create('Ext.fx.target.' + 'Element' + (useCSS3 ? 'CSS' : ''), target);
48365             me.targets.add(targetObj);
48366             return targetObj;
48367         }
48368         if (Ext.isObject(target)) {
48369             // Element
48370             if (target.dom) {
48371                 targetObj = Ext.create('Ext.fx.target.' + 'Element' + (useCSS3 ? 'CSS' : ''), target);
48372             }
48373             // Element Composite
48374             else if (target.isComposite) {
48375                 targetObj = Ext.create('Ext.fx.target.' + 'CompositeElement' + (useCSS3 ? 'CSS' : ''), target);
48376             }
48377             // Draw Sprite
48378             else if (target.isSprite) {
48379                 targetObj = Ext.create('Ext.fx.target.Sprite', target);
48380             }
48381             // Draw Sprite Composite
48382             else if (target.isCompositeSprite) {
48383                 targetObj = Ext.create('Ext.fx.target.CompositeSprite', target);
48384             }
48385             // Component
48386             else if (target.isComponent) {
48387                 targetObj = Ext.create('Ext.fx.target.Component', target);
48388             }
48389             else if (target.isAnimTarget) {
48390                 return target;
48391             }
48392             else {
48393                 return null;
48394             }
48395             me.targets.add(targetObj);
48396             return targetObj;
48397         }
48398         else {
48399             return null;
48400         }
48401     },
48402
48403     /**
48404      * Add an Anim to the manager. This is done automatically when an Anim instance is created.
48405      * @param {Ext.fx.Anim} anim
48406      */
48407     addAnim: function(anim) {
48408         var items = this.items,
48409             task = this.task;
48410         // var me = this,
48411         //     items = me.items,
48412         //     cb = function() {
48413         //         if (items.length) {
48414         //             me.task = true;
48415         //             me.runner();
48416         //             me.requestAnimFrame(cb);
48417         //         }
48418         //         else {
48419         //             me.task = false;
48420         //         }
48421         //     };
48422
48423         items.add(anim);
48424
48425         // Start the timer if not already running
48426         if (!task && items.length) {
48427             task = this.task = {
48428                 run: this.runner,
48429                 interval: this.interval,
48430                 scope: this
48431             };
48432             Ext.TaskManager.start(task);
48433         }
48434
48435         // //Start the timer if not already running
48436         // if (!me.task && items.length) {
48437         //     me.requestAnimFrame(cb);
48438         // }
48439     },
48440
48441     /**
48442      * Remove an Anim from the manager. This is done automatically when an Anim ends.
48443      * @param {Ext.fx.Anim} anim
48444      */
48445     removeAnim: function(anim) {
48446         // this.items.remove(anim);
48447         var items = this.items,
48448             task = this.task;
48449         items.remove(anim);
48450         // Stop the timer if there are no more managed Anims
48451         if (task && !items.length) {
48452             Ext.TaskManager.stop(task);
48453             delete this.task;
48454         }
48455     },
48456
48457     /**
48458      * @private
48459      * Filter function to determine which animations need to be started
48460      */
48461     startingFilter: function(o) {
48462         return o.paused === false && o.running === false && o.iterations > 0;
48463     },
48464
48465     /**
48466      * @private
48467      * Filter function to determine which animations are still running
48468      */
48469     runningFilter: function(o) {
48470         return o.paused === false && o.running === true && o.isAnimator !== true;
48471     },
48472
48473     /**
48474      * @private
48475      * Runner function being called each frame
48476      */
48477     runner: function() {
48478         var me = this,
48479             items = me.items;
48480
48481         me.targetData = {};
48482         me.targetArr = {};
48483
48484         // Single timestamp for all animations this interval
48485         me.timestamp = new Date();
48486
48487         // Start any items not current running
48488         items.filterBy(me.startingFilter).each(me.startAnim, me);
48489
48490         // Build the new attributes to be applied for all targets in this frame
48491         items.filterBy(me.runningFilter).each(me.runAnim, me);
48492
48493         // Apply all the pending changes to their targets
48494         me.applyPendingAttrs();
48495     },
48496
48497     /**
48498      * @private
48499      * Start the individual animation (initialization)
48500      */
48501     startAnim: function(anim) {
48502         anim.start(this.timestamp);
48503     },
48504
48505     /**
48506      * @private
48507      * Run the individual animation for this frame
48508      */
48509     runAnim: function(anim) {
48510         if (!anim) {
48511             return;
48512         }
48513         var me = this,
48514             targetId = anim.target.getId(),
48515             useCSS3 = me.useCSS3 && anim.target.type == 'element',
48516             elapsedTime = me.timestamp - anim.startTime,
48517             target, o;
48518
48519         this.collectTargetData(anim, elapsedTime, useCSS3);
48520
48521         // For CSS3 animation, we need to immediately set the first frame's attributes without any transition
48522         // to get a good initial state, then add the transition properties and set the final attributes.
48523         if (useCSS3) {
48524             // Flush the collected attributes, without transition
48525             anim.target.setAttr(me.targetData[targetId], true);
48526
48527             // Add the end frame data
48528             me.targetData[targetId] = [];
48529             me.collectTargetData(anim, anim.duration, useCSS3);
48530
48531             // Pause the animation so runAnim doesn't keep getting called
48532             anim.paused = true;
48533
48534             target = anim.target.target;
48535             // We only want to attach an event on the last element in a composite
48536             if (anim.target.isComposite) {
48537                 target = anim.target.target.last();
48538             }
48539
48540             // Listen for the transitionend event
48541             o = {};
48542             o[Ext.supports.CSS3TransitionEnd] = anim.lastFrame;
48543             o.scope = anim;
48544             o.single = true;
48545             target.on(o);
48546         }
48547         // For JS animation, trigger the lastFrame handler if this is the final frame
48548         else if (elapsedTime >= anim.duration) {
48549             me.applyPendingAttrs(true);
48550             delete me.targetData[targetId];
48551             delete me.targetArr[targetId];
48552             anim.lastFrame();
48553         }
48554     },
48555
48556     /**
48557      * Collect target attributes for the given Anim object at the given timestamp
48558      * @param {Ext.fx.Anim} anim The Anim instance
48559      * @param {Number} timestamp Time after the anim's start time
48560      */
48561     collectTargetData: function(anim, elapsedTime, useCSS3) {
48562         var targetId = anim.target.getId(),
48563             targetData = this.targetData[targetId],
48564             data;
48565         
48566         if (!targetData) {
48567             targetData = this.targetData[targetId] = [];
48568             this.targetArr[targetId] = anim.target;
48569         }
48570
48571         data = {
48572             duration: anim.duration,
48573             easing: (useCSS3 && anim.reverse) ? anim.easingFn.reverse().toCSS3() : anim.easing,
48574             attrs: {}
48575         };
48576         Ext.apply(data.attrs, anim.runAnim(elapsedTime));
48577         targetData.push(data);
48578     },
48579
48580     /**
48581      * @private
48582      * Apply all pending attribute changes to their targets
48583      */
48584     applyPendingAttrs: function(isLastFrame) {
48585         var targetData = this.targetData,
48586             targetArr = this.targetArr,
48587             targetId;
48588         for (targetId in targetData) {
48589             if (targetData.hasOwnProperty(targetId)) {
48590                 targetArr[targetId].setAttr(targetData[targetId], false, isLastFrame);
48591             }
48592         }
48593     }
48594 });
48595
48596 /**
48597  * @class Ext.fx.Animator
48598  * Animation instance
48599
48600 This class is used to run keyframe based animations, which follows the CSS3 based animation structure. 
48601 Keyframe animations differ from typical from/to animations in that they offer the ability to specify values 
48602 at various points throughout the animation.
48603
48604 __Using Keyframes__
48605 The {@link #keyframes} option is the most important part of specifying an animation when using this 
48606 class. A key frame is a point in a particular animation. We represent this as a percentage of the
48607 total animation duration. At each key frame, we can specify the target values at that time. Note that
48608 you *must* specify the values at 0% and 100%, the start and ending values. There is also a {@link keyframe}
48609 event that fires after each key frame is reached.
48610
48611 __Example Usage__
48612 In the example below, we modify the values of the element at each fifth throughout the animation.
48613
48614     Ext.create('Ext.fx.Animator', {
48615         target: Ext.getBody().createChild({
48616             style: {
48617                 width: '100px',
48618                 height: '100px',
48619                 'background-color': 'red'
48620             }
48621         }),
48622         duration: 10000, // 10 seconds
48623         keyframes: {
48624             0: {
48625                 opacity: 1,
48626                 backgroundColor: 'FF0000'
48627             },
48628             20: {
48629                 x: 30,
48630                 opacity: 0.5    
48631             },
48632             40: {
48633                 x: 130,
48634                 backgroundColor: '0000FF'    
48635             },
48636             60: {
48637                 y: 80,
48638                 opacity: 0.3    
48639             },
48640             80: {
48641                 width: 200,
48642                 y: 200    
48643             },
48644             100: {
48645                 opacity: 1,
48646                 backgroundColor: '00FF00'
48647             }
48648         }
48649     });
48650
48651  * @markdown
48652  */
48653 Ext.define('Ext.fx.Animator', {
48654
48655     /* Begin Definitions */
48656
48657     mixins: {
48658         observable: 'Ext.util.Observable'
48659     },
48660
48661     requires: ['Ext.fx.Manager'],
48662
48663     /* End Definitions */
48664
48665     isAnimator: true,
48666
48667     /**
48668      * @cfg {Number} duration
48669      * Time in milliseconds for the animation to last. Defaults to 250.
48670      */
48671     duration: 250,
48672
48673     /**
48674      * @cfg {Number} delay
48675      * Time to delay before starting the animation. Defaults to 0.
48676      */
48677     delay: 0,
48678
48679     /* private used to track a delayed starting time */
48680     delayStart: 0,
48681
48682     /**
48683      * @cfg {Boolean} dynamic
48684      * 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.
48685      */
48686     dynamic: false,
48687
48688     /**
48689      * @cfg {String} easing
48690
48691 This describes how the intermediate values used during a transition will be calculated. It allows for a transition to change
48692 speed over its duration. 
48693
48694 - backIn
48695 - backOut
48696 - bounceIn
48697 - bounceOut
48698 - ease
48699 - easeIn
48700 - easeOut
48701 - easeInOut
48702 - elasticIn
48703 - elasticOut
48704 - cubic-bezier(x1, y1, x2, y2)
48705
48706 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
48707 as (x1, y1, x2, y2). All values must be in the range [0, 1] or the definition is invalid.
48708
48709      * @markdown
48710      */
48711     easing: 'ease',
48712
48713     /**
48714      * Flag to determine if the animation has started
48715      * @property running
48716      * @type boolean
48717      */
48718     running: false,
48719
48720     /**
48721      * Flag to determine if the animation is paused. Only set this to true if you need to
48722      * keep the Anim instance around to be unpaused later; otherwise call {@link #end}.
48723      * @property paused
48724      * @type boolean
48725      */
48726     paused: false,
48727
48728     /**
48729      * @private
48730      */
48731     damper: 1,
48732
48733     /**
48734      * @cfg {Number} iterations
48735      * Number of times to execute the animation. Defaults to 1.
48736      */
48737     iterations: 1,
48738
48739     /**
48740      * Current iteration the animation is running.
48741      * @property currentIteration
48742      * @type int
48743      */
48744     currentIteration: 0,
48745
48746     /**
48747      * Current keyframe step of the animation.
48748      * @property keyframeStep
48749      * @type Number
48750      */
48751     keyframeStep: 0,
48752
48753     /**
48754      * @private
48755      */
48756     animKeyFramesRE: /^(from|to|\d+%?)$/,
48757
48758     /**
48759      * @cfg {Ext.fx.target} target
48760      * The Ext.fx.target to apply the animation to.  If not specified during initialization, this can be passed to the applyAnimator
48761      * method to apply the same animation to many targets.
48762      */
48763
48764      /**
48765       * @cfg {Object} keyframes
48766       * Animation keyframes follow the CSS3 Animation configuration pattern. 'from' is always considered '0%' and 'to'
48767       * is considered '100%'.<b>Every keyframe declaration must have a keyframe rule for 0% and 100%, possibly defined using
48768       * "from" or "to"</b>.  A keyframe declaration without these keyframe selectors is invalid and will not be available for
48769       * animation.  The keyframe declaration for a keyframe rule consists of properties and values. Properties that are unable to
48770       * be animated are ignored in these rules, with the exception of 'easing' which can be changed at each keyframe. For example:
48771  <pre><code>
48772 keyframes : {
48773     '0%': {
48774         left: 100
48775     },
48776     '40%': {
48777         left: 150
48778     },
48779     '60%': {
48780         left: 75
48781     },
48782     '100%': {
48783         left: 100
48784     }
48785 }
48786  </code></pre>
48787       */
48788     constructor: function(config) {
48789         var me = this;
48790         config = Ext.apply(me, config || {});
48791         me.config = config;
48792         me.id = Ext.id(null, 'ext-animator-');
48793         me.addEvents(
48794             /**
48795              * @event beforeanimate
48796              * Fires before the animation starts. A handler can return false to cancel the animation.
48797              * @param {Ext.fx.Animator} this
48798              */
48799             'beforeanimate',
48800             /**
48801               * @event keyframe
48802               * Fires at each keyframe.
48803               * @param {Ext.fx.Animator} this
48804               * @param {Number} keyframe step number
48805               */
48806             'keyframe',
48807             /**
48808              * @event afteranimate
48809              * Fires when the animation is complete.
48810              * @param {Ext.fx.Animator} this
48811              * @param {Date} startTime
48812              */
48813             'afteranimate'
48814         );
48815         me.mixins.observable.constructor.call(me, config);
48816         me.timeline = [];
48817         me.createTimeline(me.keyframes);
48818         if (me.target) {
48819             me.applyAnimator(me.target);
48820             Ext.fx.Manager.addAnim(me);
48821         }
48822     },
48823
48824     /**
48825      * @private
48826      */
48827     sorter: function (a, b) {
48828         return a.pct - b.pct;
48829     },
48830
48831     /**
48832      * @private
48833      * Takes the given keyframe configuration object and converts it into an ordered array with the passed attributes per keyframe
48834      * or applying the 'to' configuration to all keyframes.  Also calculates the proper animation duration per keyframe.
48835      */
48836     createTimeline: function(keyframes) {
48837         var me = this,
48838             attrs = [],
48839             to = me.to || {},
48840             duration = me.duration,
48841             prevMs, ms, i, ln, pct, anim, nextAnim, attr;
48842
48843         for (pct in keyframes) {
48844             if (keyframes.hasOwnProperty(pct) && me.animKeyFramesRE.test(pct)) {
48845                 attr = {attrs: Ext.apply(keyframes[pct], to)};
48846                 // CSS3 spec allow for from/to to be specified.
48847                 if (pct == "from") {
48848                     pct = 0;
48849                 }
48850                 else if (pct == "to") {
48851                     pct = 100;
48852                 }
48853                 // convert % values into integers
48854                 attr.pct = parseInt(pct, 10);
48855                 attrs.push(attr);
48856             }
48857         }
48858         // Sort by pct property
48859         Ext.Array.sort(attrs, me.sorter);
48860         // Only an end
48861         //if (attrs[0].pct) {
48862         //    attrs.unshift({pct: 0, attrs: element.attrs});
48863         //}
48864
48865         ln = attrs.length;
48866         for (i = 0; i < ln; i++) {
48867             prevMs = (attrs[i - 1]) ? duration * (attrs[i - 1].pct / 100) : 0;
48868             ms = duration * (attrs[i].pct / 100);
48869             me.timeline.push({
48870                 duration: ms - prevMs,
48871                 attrs: attrs[i].attrs
48872             });
48873         }
48874     },
48875
48876     /**
48877      * Applies animation to the Ext.fx.target
48878      * @private
48879      * @param target
48880      * @type string/object
48881      */
48882     applyAnimator: function(target) {
48883         var me = this,
48884             anims = [],
48885             timeline = me.timeline,
48886             reverse = me.reverse,
48887             ln = timeline.length,
48888             anim, easing, damper, initial, attrs, lastAttrs, i;
48889
48890         if (me.fireEvent('beforeanimate', me) !== false) {
48891             for (i = 0; i < ln; i++) {
48892                 anim = timeline[i];
48893                 attrs = anim.attrs;
48894                 easing = attrs.easing || me.easing;
48895                 damper = attrs.damper || me.damper;
48896                 delete attrs.easing;
48897                 delete attrs.damper;
48898                 anim = Ext.create('Ext.fx.Anim', {
48899                     target: target,
48900                     easing: easing,
48901                     damper: damper,
48902                     duration: anim.duration,
48903                     paused: true,
48904                     to: attrs
48905                 });
48906                 anims.push(anim);
48907             }
48908             me.animations = anims;
48909             me.target = anim.target;
48910             for (i = 0; i < ln - 1; i++) {
48911                 anim = anims[i];
48912                 anim.nextAnim = anims[i + 1];
48913                 anim.on('afteranimate', function() {
48914                     this.nextAnim.paused = false;
48915                 });
48916                 anim.on('afteranimate', function() {
48917                     this.fireEvent('keyframe', this, ++this.keyframeStep);
48918                 }, me);
48919             }
48920             anims[ln - 1].on('afteranimate', function() {
48921                 this.lastFrame();
48922             }, me);
48923         }
48924     },
48925
48926     /*
48927      * @private
48928      * Fires beforeanimate and sets the running flag.
48929      */
48930     start: function(startTime) {
48931         var me = this,
48932             delay = me.delay,
48933             delayStart = me.delayStart,
48934             delayDelta;
48935         if (delay) {
48936             if (!delayStart) {
48937                 me.delayStart = startTime;
48938                 return;
48939             }
48940             else {
48941                 delayDelta = startTime - delayStart;
48942                 if (delayDelta < delay) {
48943                     return;
48944                 }
48945                 else {
48946                     // Compensate for frame delay;
48947                     startTime = new Date(delayStart.getTime() + delay);
48948                 }
48949             }
48950         }
48951         if (me.fireEvent('beforeanimate', me) !== false) {
48952             me.startTime = startTime;
48953             me.running = true;
48954             me.animations[me.keyframeStep].paused = false;
48955         }
48956     },
48957
48958     /*
48959      * @private
48960      * Perform lastFrame cleanup and handle iterations
48961      * @returns a hash of the new attributes.
48962      */
48963     lastFrame: function() {
48964         var me = this,
48965             iter = me.iterations,
48966             iterCount = me.currentIteration;
48967
48968         iterCount++;
48969         if (iterCount < iter) {
48970             me.startTime = new Date();
48971             me.currentIteration = iterCount;
48972             me.keyframeStep = 0;
48973             me.applyAnimator(me.target);
48974             me.animations[me.keyframeStep].paused = false;
48975         }
48976         else {
48977             me.currentIteration = 0;
48978             me.end();
48979         }
48980     },
48981
48982     /*
48983      * Fire afteranimate event and end the animation. Usually called automatically when the
48984      * animation reaches its final frame, but can also be called manually to pre-emptively
48985      * stop and destroy the running animation.
48986      */
48987     end: function() {
48988         var me = this;
48989         me.fireEvent('afteranimate', me, me.startTime, new Date() - me.startTime);
48990     }
48991 });
48992 /**
48993  * @class Ext.fx.Easing
48994  * 
48995 This class contains a series of function definitions used to modify values during an animation.
48996 They describe how the intermediate values used during a transition will be calculated. It allows for a transition to change
48997 speed over its duration. The following options are available: 
48998
48999 - linear The default easing type
49000 - backIn
49001 - backOut
49002 - bounceIn
49003 - bounceOut
49004 - ease
49005 - easeIn
49006 - easeOut
49007 - easeInOut
49008 - elasticIn
49009 - elasticOut
49010 - cubic-bezier(x1, y1, x2, y2)
49011
49012 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
49013 as (x1, y1, x2, y2). All values must be in the range [0, 1] or the definition is invalid.
49014  * @markdown
49015  * @singleton
49016  */
49017 Ext.ns('Ext.fx');
49018
49019 Ext.require('Ext.fx.CubicBezier', function() {
49020     var math = Math,
49021         pi = math.PI,
49022         pow = math.pow,
49023         sin = math.sin,
49024         sqrt = math.sqrt,
49025         abs = math.abs,
49026         backInSeed = 1.70158;
49027     Ext.fx.Easing = {
49028         // ease: Ext.fx.CubicBezier.cubicBezier(0.25, 0.1, 0.25, 1),
49029         // linear: Ext.fx.CubicBezier.cubicBezier(0, 0, 1, 1),
49030         // 'ease-in': Ext.fx.CubicBezier.cubicBezier(0.42, 0, 1, 1),
49031         // 'ease-out': Ext.fx.CubicBezier.cubicBezier(0, 0.58, 1, 1),
49032         // 'ease-in-out': Ext.fx.CubicBezier.cubicBezier(0.42, 0, 0.58, 1),
49033         // 'easeIn': Ext.fx.CubicBezier.cubicBezier(0.42, 0, 1, 1),
49034         // 'easeOut': Ext.fx.CubicBezier.cubicBezier(0, 0.58, 1, 1),
49035         // 'easeInOut': Ext.fx.CubicBezier.cubicBezier(0.42, 0, 0.58, 1)
49036     };
49037
49038     Ext.apply(Ext.fx.Easing, {
49039         linear: function(n) {
49040             return n;
49041         },
49042         ease: function(n) {
49043             var q = 0.07813 - n / 2,
49044                 alpha = -0.25,
49045                 Q = sqrt(0.0066 + q * q),
49046                 x = Q - q,
49047                 X = pow(abs(x), 1/3) * (x < 0 ? -1 : 1),
49048                 y = -Q - q,
49049                 Y = pow(abs(y), 1/3) * (y < 0 ? -1 : 1),
49050                 t = X + Y + 0.25;
49051             return pow(1 - t, 2) * 3 * t * 0.1 + (1 - t) * 3 * t * t + t * t * t;
49052         },
49053         easeIn: function (n) {
49054             return pow(n, 1.7);
49055         },
49056         easeOut: function (n) {
49057             return pow(n, 0.48);
49058         },
49059         easeInOut: function(n) {
49060             var q = 0.48 - n / 1.04,
49061                 Q = sqrt(0.1734 + q * q),
49062                 x = Q - q,
49063                 X = pow(abs(x), 1/3) * (x < 0 ? -1 : 1),
49064                 y = -Q - q,
49065                 Y = pow(abs(y), 1/3) * (y < 0 ? -1 : 1),
49066                 t = X + Y + 0.5;
49067             return (1 - t) * 3 * t * t + t * t * t;
49068         },
49069         backIn: function (n) {
49070             return n * n * ((backInSeed + 1) * n - backInSeed);
49071         },
49072         backOut: function (n) {
49073             n = n - 1;
49074             return n * n * ((backInSeed + 1) * n + backInSeed) + 1;
49075         },
49076         elasticIn: function (n) {
49077             if (n === 0 || n === 1) {
49078                 return n;
49079             }
49080             var p = 0.3,
49081                 s = p / 4;
49082             return pow(2, -10 * n) * sin((n - s) * (2 * pi) / p) + 1;
49083         },
49084         elasticOut: function (n) {
49085             return 1 - Ext.fx.Easing.elasticIn(1 - n);
49086         },
49087         bounceIn: function (n) {
49088             return 1 - Ext.fx.Easing.bounceOut(1 - n);
49089         },
49090         bounceOut: function (n) {
49091             var s = 7.5625,
49092                 p = 2.75,
49093                 l;
49094             if (n < (1 / p)) {
49095                 l = s * n * n;
49096             } else {
49097                 if (n < (2 / p)) {
49098                     n -= (1.5 / p);
49099                     l = s * n * n + 0.75;
49100                 } else {
49101                     if (n < (2.5 / p)) {
49102                         n -= (2.25 / p);
49103                         l = s * n * n + 0.9375;
49104                     } else {
49105                         n -= (2.625 / p);
49106                         l = s * n * n + 0.984375;
49107                     }
49108                 }
49109             }
49110             return l;
49111         }
49112     });
49113     Ext.apply(Ext.fx.Easing, {
49114         'back-in': Ext.fx.Easing.backIn,
49115         'back-out': Ext.fx.Easing.backOut,
49116         'ease-in': Ext.fx.Easing.easeIn,
49117         'ease-out': Ext.fx.Easing.easeOut,
49118         'elastic-in': Ext.fx.Easing.elasticIn,
49119         'elastic-out': Ext.fx.Easing.elasticIn,
49120         'bounce-in': Ext.fx.Easing.bounceIn,
49121         'bounce-out': Ext.fx.Easing.bounceOut,
49122         'ease-in-out': Ext.fx.Easing.easeInOut
49123     });
49124 });
49125 /*
49126  * @class Ext.draw.Draw
49127  * Base Drawing class.  Provides base drawing functions.
49128  */
49129
49130 Ext.define('Ext.draw.Draw', {
49131     /* Begin Definitions */
49132
49133     singleton: true,
49134
49135     requires: ['Ext.draw.Color'],
49136
49137     /* End Definitions */
49138
49139     pathToStringRE: /,?([achlmqrstvxz]),?/gi,
49140     pathCommandRE: /([achlmqstvz])[\s,]*((-?\d*\.?\d*(?:e[-+]?\d+)?\s*,?\s*)+)/ig,
49141     pathValuesRE: /(-?\d*\.?\d*(?:e[-+]?\d+)?)\s*,?\s*/ig,
49142     stopsRE: /^(\d+%?)$/,
49143     radian: Math.PI / 180,
49144
49145     availableAnimAttrs: {
49146         along: "along",
49147         blur: null,
49148         "clip-rect": "csv",
49149         cx: null,
49150         cy: null,
49151         fill: "color",
49152         "fill-opacity": null,
49153         "font-size": null,
49154         height: null,
49155         opacity: null,
49156         path: "path",
49157         r: null,
49158         rotation: "csv",
49159         rx: null,
49160         ry: null,
49161         scale: "csv",
49162         stroke: "color",
49163         "stroke-opacity": null,
49164         "stroke-width": null,
49165         translation: "csv",
49166         width: null,
49167         x: null,
49168         y: null
49169     },
49170
49171     is: function(o, type) {
49172         type = String(type).toLowerCase();
49173         return (type == "object" && o === Object(o)) ||
49174             (type == "undefined" && typeof o == type) ||
49175             (type == "null" && o === null) ||
49176             (type == "array" && Array.isArray && Array.isArray(o)) ||
49177             (Object.prototype.toString.call(o).toLowerCase().slice(8, -1)) == type;
49178     },
49179
49180     ellipsePath: function(sprite) {
49181         var attr = sprite.attr;
49182         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);
49183     },
49184
49185     rectPath: function(sprite) {
49186         var attr = sprite.attr;
49187         if (attr.radius) {
49188             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);
49189         }
49190         else {
49191             return Ext.String.format("M{0},{1}l{2},0,0,{3},{4},0z", attr.x, attr.y, attr.width, attr.height, -attr.width);
49192         }
49193     },
49194
49195     path2string: function () {
49196         return this.join(",").replace(Ext.draw.Draw.pathToStringRE, "$1");
49197     },
49198
49199     parsePathString: function (pathString) {
49200         if (!pathString) {
49201             return null;
49202         }
49203         var paramCounts = {a: 7, c: 6, h: 1, l: 2, m: 2, q: 4, s: 4, t: 2, v: 1, z: 0},
49204             data = [],
49205             me = this;
49206         if (me.is(pathString, "array") && me.is(pathString[0], "array")) { // rough assumption
49207             data = me.pathClone(pathString);
49208         }
49209         if (!data.length) {
49210             String(pathString).replace(me.pathCommandRE, function (a, b, c) {
49211                 var params = [],
49212                     name = b.toLowerCase();
49213                 c.replace(me.pathValuesRE, function (a, b) {
49214                     b && params.push(+b);
49215                 });
49216                 if (name == "m" && params.length > 2) {
49217                     data.push([b].concat(params.splice(0, 2)));
49218                     name = "l";
49219                     b = (b == "m") ? "l" : "L";
49220                 }
49221                 while (params.length >= paramCounts[name]) {
49222                     data.push([b].concat(params.splice(0, paramCounts[name])));
49223                     if (!paramCounts[name]) {
49224                         break;
49225                     }
49226                 }
49227             });
49228         }
49229         data.toString = me.path2string;
49230         return data;
49231     },
49232
49233     mapPath: function (path, matrix) {
49234         if (!matrix) {
49235             return path;
49236         }
49237         var x, y, i, ii, j, jj, pathi;
49238         path = this.path2curve(path);
49239         for (i = 0, ii = path.length; i < ii; i++) {
49240             pathi = path[i];
49241             for (j = 1, jj = pathi.length; j < jj-1; j += 2) {
49242                 x = matrix.x(pathi[j], pathi[j + 1]);
49243                 y = matrix.y(pathi[j], pathi[j + 1]);
49244                 pathi[j] = x;
49245                 pathi[j + 1] = y;
49246             }
49247         }
49248         return path;
49249     },
49250
49251     pathClone: function(pathArray) {
49252         var res = [],
49253             j,
49254             jj,
49255             i,
49256             ii;
49257         if (!this.is(pathArray, "array") || !this.is(pathArray && pathArray[0], "array")) { // rough assumption
49258             pathArray = this.parsePathString(pathArray);
49259         }
49260         for (i = 0, ii = pathArray.length; i < ii; i++) {
49261             res[i] = [];
49262             for (j = 0, jj = pathArray[i].length; j < jj; j++) {
49263                 res[i][j] = pathArray[i][j];
49264             }
49265         }
49266         res.toString = this.path2string;
49267         return res;
49268     },
49269
49270     pathToAbsolute: function (pathArray) {
49271         if (!this.is(pathArray, "array") || !this.is(pathArray && pathArray[0], "array")) { // rough assumption
49272             pathArray = this.parsePathString(pathArray);
49273         }
49274         var res = [],
49275             x = 0,
49276             y = 0,
49277             mx = 0,
49278             my = 0,
49279             start = 0,
49280             i,
49281             ii,
49282             r,
49283             pa,
49284             j,
49285             jj,
49286             k,
49287             kk;
49288         if (pathArray[0][0] == "M") {
49289             x = +pathArray[0][1];
49290             y = +pathArray[0][2];
49291             mx = x;
49292             my = y;
49293             start++;
49294             res[0] = ["M", x, y];
49295         }
49296         for (i = start, ii = pathArray.length; i < ii; i++) {
49297             r = res[i] = [];
49298             pa = pathArray[i];
49299             if (pa[0] != pa[0].toUpperCase()) {
49300                 r[0] = pa[0].toUpperCase();
49301                 switch (r[0]) {
49302                     case "A":
49303                         r[1] = pa[1];
49304                         r[2] = pa[2];
49305                         r[3] = pa[3];
49306                         r[4] = pa[4];
49307                         r[5] = pa[5];
49308                         r[6] = +(pa[6] + x);
49309                         r[7] = +(pa[7] + y);
49310                         break;
49311                     case "V":
49312                         r[1] = +pa[1] + y;
49313                         break;
49314                     case "H":
49315                         r[1] = +pa[1] + x;
49316                         break;
49317                     case "M":
49318                         mx = +pa[1] + x;
49319                         my = +pa[2] + y;
49320                     default:
49321                         for (j = 1, jj = pa.length; j < jj; j++) {
49322                             r[j] = +pa[j] + ((j % 2) ? x : y);
49323                         }
49324                 }
49325             } else {
49326                 for (k = 0, kk = pa.length; k < kk; k++) {
49327                     res[i][k] = pa[k];
49328                 }
49329             }
49330             switch (r[0]) {
49331                 case "Z":
49332                     x = mx;
49333                     y = my;
49334                     break;
49335                 case "H":
49336                     x = r[1];
49337                     break;
49338                 case "V":
49339                     y = r[1];
49340                     break;
49341                 case "M":
49342                     mx = res[i][res[i].length - 2];
49343                     my = res[i][res[i].length - 1];
49344                 default:
49345                     x = res[i][res[i].length - 2];
49346                     y = res[i][res[i].length - 1];
49347             }
49348         }
49349         res.toString = this.path2string;
49350         return res;
49351     },
49352
49353     pathToRelative: function (pathArray) {
49354         if (!this.is(pathArray, "array") || !this.is(pathArray && pathArray[0], "array")) {
49355             pathArray = this.parsePathString(pathArray);
49356         }
49357         var res = [],
49358             x = 0,
49359             y = 0,
49360             mx = 0,
49361             my = 0,
49362             start = 0;
49363         if (pathArray[0][0] == "M") {
49364             x = pathArray[0][1];
49365             y = pathArray[0][2];
49366             mx = x;
49367             my = y;
49368             start++;
49369             res.push(["M", x, y]);
49370         }
49371         for (var i = start, ii = pathArray.length; i < ii; i++) {
49372             var r = res[i] = [],
49373                 pa = pathArray[i];
49374             if (pa[0] != pa[0].toLowerCase()) {
49375                 r[0] = pa[0].toLowerCase();
49376                 switch (r[0]) {
49377                     case "a":
49378                         r[1] = pa[1];
49379                         r[2] = pa[2];
49380                         r[3] = pa[3];
49381                         r[4] = pa[4];
49382                         r[5] = pa[5];
49383                         r[6] = +(pa[6] - x).toFixed(3);
49384                         r[7] = +(pa[7] - y).toFixed(3);
49385                         break;
49386                     case "v":
49387                         r[1] = +(pa[1] - y).toFixed(3);
49388                         break;
49389                     case "m":
49390                         mx = pa[1];
49391                         my = pa[2];
49392                     default:
49393                         for (var j = 1, jj = pa.length; j < jj; j++) {
49394                             r[j] = +(pa[j] - ((j % 2) ? x : y)).toFixed(3);
49395                         }
49396                 }
49397             } else {
49398                 r = res[i] = [];
49399                 if (pa[0] == "m") {
49400                     mx = pa[1] + x;
49401                     my = pa[2] + y;
49402                 }
49403                 for (var k = 0, kk = pa.length; k < kk; k++) {
49404                     res[i][k] = pa[k];
49405                 }
49406             }
49407             var len = res[i].length;
49408             switch (res[i][0]) {
49409                 case "z":
49410                     x = mx;
49411                     y = my;
49412                     break;
49413                 case "h":
49414                     x += +res[i][len - 1];
49415                     break;
49416                 case "v":
49417                     y += +res[i][len - 1];
49418                     break;
49419                 default:
49420                     x += +res[i][len - 2];
49421                     y += +res[i][len - 1];
49422             }
49423         }
49424         res.toString = this.path2string;
49425         return res;
49426     },
49427
49428     //Returns a path converted to a set of curveto commands
49429     path2curve: function (path) {
49430         var me = this,
49431             points = me.pathToAbsolute(path),
49432             ln = points.length,
49433             attrs = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
49434             i, seg, segLn, point;
49435             
49436         for (i = 0; i < ln; i++) {
49437             points[i] = me.command2curve(points[i], attrs);
49438             if (points[i].length > 7) {
49439                     points[i].shift();
49440                     point = points[i];
49441                     while (point.length) {
49442                         points.splice(i++, 0, ["C"].concat(point.splice(0, 6)));
49443                     }
49444                     points.splice(i, 1);
49445                     ln = points.length;
49446                 }
49447             seg = points[i];
49448             segLn = seg.length;
49449             attrs.x = seg[segLn - 2];
49450             attrs.y = seg[segLn - 1];
49451             attrs.bx = parseFloat(seg[segLn - 4]) || attrs.x;
49452             attrs.by = parseFloat(seg[segLn - 3]) || attrs.y;
49453         }
49454         return points;
49455     },
49456     
49457     interpolatePaths: function (path, path2) {
49458         var me = this,
49459             p = me.pathToAbsolute(path),
49460             p2 = me.pathToAbsolute(path2),
49461             attrs = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
49462             attrs2 = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
49463             fixArc = function (pp, i) {
49464                 if (pp[i].length > 7) {
49465                     pp[i].shift();
49466                     var pi = pp[i];
49467                     while (pi.length) {
49468                         pp.splice(i++, 0, ["C"].concat(pi.splice(0, 6)));
49469                     }
49470                     pp.splice(i, 1);
49471                     ii = Math.max(p.length, p2.length || 0);
49472                 }
49473             },
49474             fixM = function (path1, path2, a1, a2, i) {
49475                 if (path1 && path2 && path1[i][0] == "M" && path2[i][0] != "M") {
49476                     path2.splice(i, 0, ["M", a2.x, a2.y]);
49477                     a1.bx = 0;
49478                     a1.by = 0;
49479                     a1.x = path1[i][1];
49480                     a1.y = path1[i][2];
49481                     ii = Math.max(p.length, p2.length || 0);
49482                 }
49483             };
49484         for (var i = 0, ii = Math.max(p.length, p2.length || 0); i < ii; i++) {
49485             p[i] = me.command2curve(p[i], attrs);
49486             fixArc(p, i);
49487             (p2[i] = me.command2curve(p2[i], attrs2));
49488             fixArc(p2, i);
49489             fixM(p, p2, attrs, attrs2, i);
49490             fixM(p2, p, attrs2, attrs, i);
49491             var seg = p[i],
49492                 seg2 = p2[i],
49493                 seglen = seg.length,
49494                 seg2len = seg2.length;
49495             attrs.x = seg[seglen - 2];
49496             attrs.y = seg[seglen - 1];
49497             attrs.bx = parseFloat(seg[seglen - 4]) || attrs.x;
49498             attrs.by = parseFloat(seg[seglen - 3]) || attrs.y;
49499             attrs2.bx = (parseFloat(seg2[seg2len - 4]) || attrs2.x);
49500             attrs2.by = (parseFloat(seg2[seg2len - 3]) || attrs2.y);
49501             attrs2.x = seg2[seg2len - 2];
49502             attrs2.y = seg2[seg2len - 1];
49503         }
49504         return [p, p2];
49505     },
49506     
49507     //Returns any path command as a curveto command based on the attrs passed
49508     command2curve: function (pathCommand, d) {
49509         var me = this;
49510         if (!pathCommand) {
49511             return ["C", d.x, d.y, d.x, d.y, d.x, d.y];
49512         }
49513         if (pathCommand[0] != "T" && pathCommand[0] != "Q") {
49514             d.qx = d.qy = null;
49515         }
49516         switch (pathCommand[0]) {
49517             case "M":
49518                 d.X = pathCommand[1];
49519                 d.Y = pathCommand[2];
49520                 break;
49521             case "A":
49522                 pathCommand = ["C"].concat(me.arc2curve.apply(me, [d.x, d.y].concat(pathCommand.slice(1))));
49523                 break;
49524             case "S":
49525                 pathCommand = ["C", d.x + (d.x - (d.bx || d.x)), d.y + (d.y - (d.by || d.y))].concat(pathCommand.slice(1));
49526                 break;
49527             case "T":
49528                 d.qx = d.x + (d.x - (d.qx || d.x));
49529                 d.qy = d.y + (d.y - (d.qy || d.y));
49530                 pathCommand = ["C"].concat(me.quadratic2curve(d.x, d.y, d.qx, d.qy, pathCommand[1], pathCommand[2]));
49531                 break;
49532             case "Q":
49533                 d.qx = pathCommand[1];
49534                 d.qy = pathCommand[2];
49535                 pathCommand = ["C"].concat(me.quadratic2curve(d.x, d.y, pathCommand[1], pathCommand[2], pathCommand[3], pathCommand[4]));
49536                 break;
49537             case "L":
49538                 pathCommand = ["C"].concat(d.x, d.y, pathCommand[1], pathCommand[2], pathCommand[1], pathCommand[2]);
49539                 break;
49540             case "H":
49541                 pathCommand = ["C"].concat(d.x, d.y, pathCommand[1], d.y, pathCommand[1], d.y);
49542                 break;
49543             case "V":
49544                 pathCommand = ["C"].concat(d.x, d.y, d.x, pathCommand[1], d.x, pathCommand[1]);
49545                 break;
49546             case "Z":
49547                 pathCommand = ["C"].concat(d.x, d.y, d.X, d.Y, d.X, d.Y);
49548                 break;
49549         }
49550         return pathCommand;
49551     },
49552
49553     quadratic2curve: function (x1, y1, ax, ay, x2, y2) {
49554         var _13 = 1 / 3,
49555             _23 = 2 / 3;
49556         return [
49557                 _13 * x1 + _23 * ax,
49558                 _13 * y1 + _23 * ay,
49559                 _13 * x2 + _23 * ax,
49560                 _13 * y2 + _23 * ay,
49561                 x2,
49562                 y2
49563             ];
49564     },
49565     
49566     rotate: function (x, y, rad) {
49567         var cos = Math.cos(rad),
49568             sin = Math.sin(rad),
49569             X = x * cos - y * sin,
49570             Y = x * sin + y * cos;
49571         return {x: X, y: Y};
49572     },
49573
49574     arc2curve: function (x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) {
49575         // for more information of where this Math came from visit:
49576         // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
49577         var me = this,
49578             PI = Math.PI,
49579             radian = me.radian,
49580             _120 = PI * 120 / 180,
49581             rad = radian * (+angle || 0),
49582             res = [],
49583             math = Math,
49584             mcos = math.cos,
49585             msin = math.sin,
49586             msqrt = math.sqrt,
49587             mabs = math.abs,
49588             masin = math.asin,
49589             xy, cos, sin, x, y, h, rx2, ry2, k, cx, cy, f1, f2, df, c1, s1, c2, s2,
49590             t, hx, hy, m1, m2, m3, m4, newres, i, ln, f2old, x2old, y2old;
49591         if (!recursive) {
49592             xy = me.rotate(x1, y1, -rad);
49593             x1 = xy.x;
49594             y1 = xy.y;
49595             xy = me.rotate(x2, y2, -rad);
49596             x2 = xy.x;
49597             y2 = xy.y;
49598             cos = mcos(radian * angle);
49599             sin = msin(radian * angle);
49600             x = (x1 - x2) / 2;
49601             y = (y1 - y2) / 2;
49602             h = (x * x) / (rx * rx) + (y * y) / (ry * ry);
49603             if (h > 1) {
49604                 h = msqrt(h);
49605                 rx = h * rx;
49606                 ry = h * ry;
49607             }
49608             rx2 = rx * rx;
49609             ry2 = ry * ry;
49610             k = (large_arc_flag == sweep_flag ? -1 : 1) *
49611                     msqrt(mabs((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x)));
49612             cx = k * rx * y / ry + (x1 + x2) / 2;
49613             cy = k * -ry * x / rx + (y1 + y2) / 2;
49614             f1 = masin(((y1 - cy) / ry).toFixed(7));
49615             f2 = masin(((y2 - cy) / ry).toFixed(7));
49616
49617             f1 = x1 < cx ? PI - f1 : f1;
49618             f2 = x2 < cx ? PI - f2 : f2;
49619             if (f1 < 0) {
49620                 f1 = PI * 2 + f1;
49621             }
49622             if (f2 < 0) {
49623                 f2 = PI * 2 + f2;
49624             }
49625             if (sweep_flag && f1 > f2) {
49626                 f1 = f1 - PI * 2;
49627             }
49628             if (!sweep_flag && f2 > f1) {
49629                 f2 = f2 - PI * 2;
49630             }
49631         }
49632         else {
49633             f1 = recursive[0];
49634             f2 = recursive[1];
49635             cx = recursive[2];
49636             cy = recursive[3];
49637         }
49638         df = f2 - f1;
49639         if (mabs(df) > _120) {
49640             f2old = f2;
49641             x2old = x2;
49642             y2old = y2;
49643             f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1);
49644             x2 = cx + rx * mcos(f2);
49645             y2 = cy + ry * msin(f2);
49646             res = me.arc2curve(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [f2, f2old, cx, cy]);
49647         }
49648         df = f2 - f1;
49649         c1 = mcos(f1);
49650         s1 = msin(f1);
49651         c2 = mcos(f2);
49652         s2 = msin(f2);
49653         t = math.tan(df / 4);
49654         hx = 4 / 3 * rx * t;
49655         hy = 4 / 3 * ry * t;
49656         m1 = [x1, y1];
49657         m2 = [x1 + hx * s1, y1 - hy * c1];
49658         m3 = [x2 + hx * s2, y2 - hy * c2];
49659         m4 = [x2, y2];
49660         m2[0] = 2 * m1[0] - m2[0];
49661         m2[1] = 2 * m1[1] - m2[1];
49662         if (recursive) {
49663             return [m2, m3, m4].concat(res);
49664         }
49665         else {
49666             res = [m2, m3, m4].concat(res).join().split(",");
49667             newres = [];
49668             ln = res.length;
49669             for (i = 0;  i < ln; i++) {
49670                 newres[i] = i % 2 ? me.rotate(res[i - 1], res[i], rad).y : me.rotate(res[i], res[i + 1], rad).x;
49671             }
49672             return newres;
49673         }
49674     },
49675     
49676     rotatePoint: function (x, y, alpha, cx, cy) {
49677         if (!alpha) {
49678             return {
49679                 x: x,
49680                 y: y
49681             };
49682         }
49683         cx = cx || 0;
49684         cy = cy || 0;
49685         x = x - cx;
49686         y = y - cy;
49687         alpha = alpha * this.radian;
49688         var cos = Math.cos(alpha),
49689             sin = Math.sin(alpha);
49690         return {
49691             x: x * cos - y * sin + cx,
49692             y: x * sin + y * cos + cy
49693         };
49694     },
49695
49696     rotateAndTranslatePath: function (sprite) {
49697         var alpha = sprite.rotation.degrees,
49698             cx = sprite.rotation.x,
49699             cy = sprite.rotation.y,
49700             dx = sprite.translation.x,
49701             dy = sprite.translation.y,
49702             path,
49703             i,
49704             p,
49705             xy,
49706             j,
49707             res = [];
49708         if (!alpha && !dx && !dy) {
49709             return this.pathToAbsolute(sprite.attr.path);
49710         }
49711         dx = dx || 0;
49712         dy = dy || 0;
49713         path = this.pathToAbsolute(sprite.attr.path);
49714         for (i = path.length; i--;) {
49715             p = res[i] = path[i].slice();
49716             if (p[0] == "A") {
49717                 xy = this.rotatePoint(p[6], p[7], alpha, cx, cy);
49718                 p[6] = xy.x + dx;
49719                 p[7] = xy.y + dy;
49720             } else {
49721                 j = 1;
49722                 while (p[j + 1] != null) {
49723                     xy = this.rotatePoint(p[j], p[j + 1], alpha, cx, cy);
49724                     p[j] = xy.x + dx;
49725                     p[j + 1] = xy.y + dy;
49726                     j += 2;
49727                 }
49728             }
49729         }
49730         return res;
49731     },
49732     
49733     pathDimensions: function (path) {
49734         if (!path || !(path + "")) {
49735             return {x: 0, y: 0, width: 0, height: 0};
49736         }
49737         path = this.path2curve(path);
49738         var x = 0, 
49739             y = 0,
49740             X = [],
49741             Y = [],
49742             p,
49743             i,
49744             ii,
49745             xmin,
49746             ymin,
49747             dim;
49748         for (i = 0, ii = path.length; i < ii; i++) {
49749             p = path[i];
49750             if (p[0] == "M") {
49751                 x = p[1];
49752                 y = p[2];
49753                 X.push(x);
49754                 Y.push(y);
49755             }
49756             else {
49757                 dim = this.curveDim(x, y, p[1], p[2], p[3], p[4], p[5], p[6]);
49758                 X = X.concat(dim.min.x, dim.max.x);
49759                 Y = Y.concat(dim.min.y, dim.max.y);
49760                 x = p[5];
49761                 y = p[6];
49762             }
49763         }
49764         xmin = Math.min.apply(0, X);
49765         ymin = Math.min.apply(0, Y);
49766         return {
49767             x: xmin,
49768             y: ymin,
49769             path: path,
49770             width: Math.max.apply(0, X) - xmin,
49771             height: Math.max.apply(0, Y) - ymin
49772         };
49773     },
49774     
49775     intersect: function(subjectPolygon, clipPolygon) {
49776         var cp1, cp2, s, e, point;
49777         var inside = function(p) {
49778             return (cp2[0]-cp1[0]) * (p[1]-cp1[1]) > (cp2[1]-cp1[1]) * (p[0]-cp1[0]);
49779         };
49780         var intersection = function() {
49781             var p = [];
49782             var dcx = cp1[0]-cp2[0],
49783                 dcy = cp1[1]-cp2[1],
49784                 dpx = s[0]-e[0],
49785                 dpy = s[1]-e[1],
49786                 n1 = cp1[0]*cp2[1] - cp1[1]*cp2[0],
49787                 n2 = s[0]*e[1] - s[1]*e[0],
49788                 n3 = 1 / (dcx*dpy - dcy*dpx);
49789
49790             p[0] = (n1*dpx - n2*dcx) * n3;
49791             p[1] = (n1*dpy - n2*dcy) * n3;
49792             return p;
49793         };
49794         var outputList = subjectPolygon;
49795         cp1 = clipPolygon[clipPolygon.length -1];
49796         for (var i = 0, l = clipPolygon.length; i < l; ++i) {
49797             cp2 = clipPolygon[i];
49798             var inputList = outputList;
49799             outputList = [];
49800             s = inputList[inputList.length -1];
49801             for (var j = 0, ln = inputList.length; j < ln; j++) {
49802                 e = inputList[j];
49803                 if (inside(e)) {
49804                     if (!inside(s)) {
49805                         outputList.push(intersection());
49806                     }
49807                     outputList.push(e);
49808                 } else if (inside(s)) {
49809                     outputList.push(intersection());
49810                 }
49811                 s = e;
49812             }
49813             cp1 = cp2;
49814         }
49815         return outputList;
49816     },
49817     
49818     curveDim: function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
49819         var a = (c2x - 2 * c1x + p1x) - (p2x - 2 * c2x + c1x),
49820             b = 2 * (c1x - p1x) - 2 * (c2x - c1x),
49821             c = p1x - c1x,
49822             t1 = (-b + Math.sqrt(b * b - 4 * a * c)) / 2 / a,
49823             t2 = (-b - Math.sqrt(b * b - 4 * a * c)) / 2 / a,
49824             y = [p1y, p2y],
49825             x = [p1x, p2x],
49826             dot;
49827         if (Math.abs(t1) > 1e12) {
49828             t1 = 0.5;
49829         }
49830         if (Math.abs(t2) > 1e12) {
49831             t2 = 0.5;
49832         }
49833         if (t1 > 0 && t1 < 1) {
49834             dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1);
49835             x.push(dot.x);
49836             y.push(dot.y);
49837         }
49838         if (t2 > 0 && t2 < 1) {
49839             dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2);
49840             x.push(dot.x);
49841             y.push(dot.y);
49842         }
49843         a = (c2y - 2 * c1y + p1y) - (p2y - 2 * c2y + c1y);
49844         b = 2 * (c1y - p1y) - 2 * (c2y - c1y);
49845         c = p1y - c1y;
49846         t1 = (-b + Math.sqrt(b * b - 4 * a * c)) / 2 / a;
49847         t2 = (-b - Math.sqrt(b * b - 4 * a * c)) / 2 / a;
49848         if (Math.abs(t1) > 1e12) {
49849             t1 = 0.5;
49850         }
49851         if (Math.abs(t2) > 1e12) {
49852             t2 = 0.5;
49853         }
49854         if (t1 > 0 && t1 < 1) {
49855             dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1);
49856             x.push(dot.x);
49857             y.push(dot.y);
49858         }
49859         if (t2 > 0 && t2 < 1) {
49860             dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2);
49861             x.push(dot.x);
49862             y.push(dot.y);
49863         }
49864         return {
49865             min: {x: Math.min.apply(0, x), y: Math.min.apply(0, y)},
49866             max: {x: Math.max.apply(0, x), y: Math.max.apply(0, y)}
49867         };
49868     },
49869
49870     getAnchors: function (p1x, p1y, p2x, p2y, p3x, p3y, value) {
49871         value = value || 4;
49872         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),
49873             a = Math.atan((p2x - p1x) / Math.abs(p2y - p1y)),
49874             b = Math.atan((p3x - p2x) / Math.abs(p2y - p3y)),
49875             pi = Math.PI;
49876         a = p1y < p2y ? pi - a : a;
49877         b = p3y < p2y ? pi - b : b;
49878         var alpha = pi / 2 - ((a + b) % (pi * 2)) / 2;
49879         alpha > pi / 2 && (alpha -= pi);
49880         var dx1 = l * Math.sin(alpha + a),
49881             dy1 = l * Math.cos(alpha + a),
49882             dx2 = l * Math.sin(alpha + b),
49883             dy2 = l * Math.cos(alpha + b),
49884             out = {
49885                 x1: p2x - dx1,
49886                 y1: p2y + dy1,
49887                 x2: p2x + dx2,
49888                 y2: p2y + dy2
49889             };
49890         return out;
49891     },
49892
49893     /* Smoothing function for a path.  Converts a path into cubic beziers.  Value defines the divider of the distance between points.
49894      * Defaults to a value of 4.
49895      */
49896     smooth: function (originalPath, value) {
49897         var path = this.path2curve(originalPath),
49898             newp = [path[0]],
49899             x = path[0][1],
49900             y = path[0][2],
49901             j,
49902             points,
49903             i = 1,
49904             ii = path.length,
49905             beg = 1,
49906             mx = x,
49907             my = y,
49908             cx = 0,
49909             cy = 0;
49910         for (; i < ii; i++) {
49911             var pathi = path[i],
49912                 pathil = pathi.length,
49913                 pathim = path[i - 1],
49914                 pathiml = pathim.length,
49915                 pathip = path[i + 1],
49916                 pathipl = pathip && pathip.length;
49917             if (pathi[0] == "M") {
49918                 mx = pathi[1];
49919                 my = pathi[2];
49920                 j = i + 1;
49921                 while (path[j][0] != "C") {
49922                     j++;
49923                 }
49924                 cx = path[j][5];
49925                 cy = path[j][6];
49926                 newp.push(["M", mx, my]);
49927                 beg = newp.length;
49928                 x = mx;
49929                 y = my;
49930                 continue;
49931             }
49932             if (pathi[pathil - 2] == mx && pathi[pathil - 1] == my && (!pathip || pathip[0] == "M")) {
49933                 var begl = newp[beg].length;
49934                 points = this.getAnchors(pathim[pathiml - 2], pathim[pathiml - 1], mx, my, newp[beg][begl - 2], newp[beg][begl - 1], value);
49935                 newp[beg][1] = points.x2;
49936                 newp[beg][2] = points.y2;
49937             }
49938             else if (!pathip || pathip[0] == "M") {
49939                 points = {
49940                     x1: pathi[pathil - 2],
49941                     y1: pathi[pathil - 1]
49942                 };
49943             } else {
49944                 points = this.getAnchors(pathim[pathiml - 2], pathim[pathiml - 1], pathi[pathil - 2], pathi[pathil - 1], pathip[pathipl - 2], pathip[pathipl - 1], value);
49945             }
49946             newp.push(["C", x, y, points.x1, points.y1, pathi[pathil - 2], pathi[pathil - 1]]);
49947             x = points.x2;
49948             y = points.y2;
49949         }
49950         return newp;
49951     },
49952
49953     findDotAtSegment: function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
49954         var t1 = 1 - t;
49955         return {
49956             x: Math.pow(t1, 3) * p1x + Math.pow(t1, 2) * 3 * t * c1x + t1 * 3 * t * t * c2x + Math.pow(t, 3) * p2x,
49957             y: Math.pow(t1, 3) * p1y + Math.pow(t1, 2) * 3 * t * c1y + t1 * 3 * t * t * c2y + Math.pow(t, 3) * p2y
49958         };
49959     },
49960
49961     snapEnds: function (from, to, stepsMax) {
49962         var step = (to - from) / stepsMax,
49963             level = Math.floor(Math.log(step) / Math.LN10) + 1,
49964             m = Math.pow(10, level),
49965             cur,
49966             modulo = Math.round((step % m) * Math.pow(10, 2 - level)),
49967             interval = [[0, 15], [20, 4], [30, 2], [40, 4], [50, 9], [60, 4], [70, 2], [80, 4], [100, 15]],
49968             stepCount = 0,
49969             value,
49970             weight,
49971             i,
49972             topValue,
49973             topWeight = 1e9,
49974             ln = interval.length;
49975         cur = from = Math.floor(from / m) * m;
49976         for (i = 0; i < ln; i++) {
49977             value = interval[i][0];
49978             weight = (value - modulo) < 0 ? 1e6 : (value - modulo) / interval[i][1];
49979             if (weight < topWeight) {
49980                 topValue = value;
49981                 topWeight = weight;
49982             }
49983         }
49984         step = Math.floor(step * Math.pow(10, -level)) * Math.pow(10, level) + topValue * Math.pow(10, level - 2);
49985         while (cur < to) {
49986             cur += step;
49987             stepCount++;
49988         }
49989         to = +cur.toFixed(10);
49990         return {
49991             from: from,
49992             to: to,
49993             power: level,
49994             step: step,
49995             steps: stepCount
49996         };
49997     },
49998
49999     sorter: function (a, b) {
50000         return a.offset - b.offset;
50001     },
50002
50003     rad: function(degrees) {
50004         return degrees % 360 * Math.PI / 180;
50005     },
50006
50007     degrees: function(radian) {
50008         return radian * 180 / Math.PI % 360;
50009     },
50010
50011     withinBox: function(x, y, bbox) {
50012         bbox = bbox || {};
50013         return (x >= bbox.x && x <= (bbox.x + bbox.width) && y >= bbox.y && y <= (bbox.y + bbox.height));
50014     },
50015
50016     parseGradient: function(gradient) {
50017         var me = this,
50018             type = gradient.type || 'linear',
50019             angle = gradient.angle || 0,
50020             radian = me.radian,
50021             stops = gradient.stops,
50022             stopsArr = [],
50023             stop,
50024             vector,
50025             max,
50026             stopObj;
50027
50028         if (type == 'linear') {
50029             vector = [0, 0, Math.cos(angle * radian), Math.sin(angle * radian)];
50030             max = 1 / (Math.max(Math.abs(vector[2]), Math.abs(vector[3])) || 1);
50031             vector[2] *= max;
50032             vector[3] *= max;
50033             if (vector[2] < 0) {
50034                 vector[0] = -vector[2];
50035                 vector[2] = 0;
50036             }
50037             if (vector[3] < 0) {
50038                 vector[1] = -vector[3];
50039                 vector[3] = 0;
50040             }
50041         }
50042
50043         for (stop in stops) {
50044             if (stops.hasOwnProperty(stop) && me.stopsRE.test(stop)) {
50045                 stopObj = {
50046                     offset: parseInt(stop, 10),
50047                     color: Ext.draw.Color.toHex(stops[stop].color) || '#ffffff',
50048                     opacity: stops[stop].opacity || 1
50049                 };
50050                 stopsArr.push(stopObj);
50051             }
50052         }
50053         // Sort by pct property
50054         Ext.Array.sort(stopsArr, me.sorter);
50055         if (type == 'linear') {
50056             return {
50057                 id: gradient.id,
50058                 type: type,
50059                 vector: vector,
50060                 stops: stopsArr
50061             };
50062         }
50063         else {
50064             return {
50065                 id: gradient.id,
50066                 type: type,
50067                 centerX: gradient.centerX,
50068                 centerY: gradient.centerY,
50069                 focalX: gradient.focalX,
50070                 focalY: gradient.focalY,
50071                 radius: gradient.radius,
50072                 vector: vector,
50073                 stops: stopsArr
50074             };
50075         }
50076     }
50077 });
50078
50079 /**
50080  * @class Ext.fx.PropertyHandler
50081  * @ignore
50082  */
50083 Ext.define('Ext.fx.PropertyHandler', {
50084
50085     /* Begin Definitions */
50086
50087     requires: ['Ext.draw.Draw'],
50088
50089     statics: {
50090         defaultHandler: {
50091             pixelDefaults: ['width', 'height', 'top', 'left'],
50092             unitRE: /^(-?\d*\.?\d*){1}(em|ex|px|in|cm|mm|pt|pc|%)*$/,
50093
50094             computeDelta: function(from, end, damper, initial, attr) {
50095                 damper = (typeof damper == 'number') ? damper : 1;
50096                 var match = this.unitRE.exec(from),
50097                     start, units;
50098                 if (match) {
50099                     from = match[1];
50100                     units = match[2];
50101                     if (!units && Ext.Array.contains(this.pixelDefaults, attr)) {
50102                         units = 'px';
50103                     }
50104                 }
50105                 from = +from || 0;
50106
50107                 match = this.unitRE.exec(end);
50108                 if (match) {
50109                     end = match[1];
50110                     units = match[2] || units;
50111                 }
50112                 end = +end || 0;
50113                 start = (initial != null) ? initial : from;
50114                 return {
50115                     from: from,
50116                     delta: (end - start) * damper,
50117                     units: units
50118                 };
50119             },
50120
50121             get: function(from, end, damper, initialFrom, attr) {
50122                 var ln = from.length,
50123                     out = [],
50124                     i, initial, res, j, len;
50125                 for (i = 0; i < ln; i++) {
50126                     if (initialFrom) {
50127                         initial = initialFrom[i][1].from;
50128                     }
50129                     if (Ext.isArray(from[i][1]) && Ext.isArray(end)) {
50130                         res = [];
50131                         j = 0;
50132                         len = from[i][1].length;
50133                         for (; j < len; j++) {
50134                             res.push(this.computeDelta(from[i][1][j], end[j], damper, initial, attr));
50135                         }
50136                         out.push([from[i][0], res]);
50137                     }
50138                     else {
50139                         out.push([from[i][0], this.computeDelta(from[i][1], end, damper, initial, attr)]);
50140                     }
50141                 }
50142                 return out;
50143             },
50144
50145             set: function(values, easing) {
50146                 var ln = values.length,
50147                     out = [],
50148                     i, val, res, len, j;
50149                 for (i = 0; i < ln; i++) {
50150                     val  = values[i][1];
50151                     if (Ext.isArray(val)) {
50152                         res = [];
50153                         j = 0;
50154                         len = val.length;
50155                         for (; j < len; j++) {
50156                             res.push(val[j].from + (val[j].delta * easing) + (val[j].units || 0));
50157                         }
50158                         out.push([values[i][0], res]);
50159                     } else {
50160                         out.push([values[i][0], val.from + (val.delta * easing) + (val.units || 0)]);
50161                     }
50162                 }
50163                 return out;
50164             }
50165         },
50166         color: {
50167             rgbRE: /^rgb\(([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\)$/i,
50168             hexRE: /^#?([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$/i,
50169             hex3RE: /^#?([0-9A-F]{1})([0-9A-F]{1})([0-9A-F]{1})$/i,
50170
50171             parseColor : function(color, damper) {
50172                 damper = (typeof damper == 'number') ? damper : 1;
50173                 var base,
50174                     out = false,
50175                     match;
50176
50177                 Ext.each([this.hexRE, this.rgbRE, this.hex3RE], function(re, idx) {
50178                     base = (idx % 2 == 0) ? 16 : 10;
50179                     match = re.exec(color);
50180                     if (match && match.length == 4) {
50181                         if (idx == 2) {
50182                             match[1] += match[1];
50183                             match[2] += match[2];
50184                             match[3] += match[3];
50185                         }
50186                         out = {
50187                             red: parseInt(match[1], base),
50188                             green: parseInt(match[2], base),
50189                             blue: parseInt(match[3], base)
50190                         };
50191                         return false;
50192                     }
50193                 });
50194                 return out || color;
50195             },
50196
50197             computeDelta: function(from, end, damper, initial) {
50198                 from = this.parseColor(from);
50199                 end = this.parseColor(end, damper);
50200                 var start = initial ? initial : from,
50201                     tfrom = typeof start,
50202                     tend = typeof end;
50203                 //Extra check for when the color string is not recognized.
50204                 if (tfrom == 'string' ||  tfrom == 'undefined' 
50205                   || tend == 'string' || tend == 'undefined') {
50206                     return end || start;
50207                 }
50208                 return {
50209                     from:  from,
50210                     delta: {
50211                         red: Math.round((end.red - start.red) * damper),
50212                         green: Math.round((end.green - start.green) * damper),
50213                         blue: Math.round((end.blue - start.blue) * damper)
50214                     }
50215                 };
50216             },
50217
50218             get: function(start, end, damper, initialFrom) {
50219                 var ln = start.length,
50220                     out = [],
50221                     i, initial;
50222                 for (i = 0; i < ln; i++) {
50223                     if (initialFrom) {
50224                         initial = initialFrom[i][1].from;
50225                     }
50226                     out.push([start[i][0], this.computeDelta(start[i][1], end, damper, initial)]);
50227                 }
50228                 return out;
50229             },
50230
50231             set: function(values, easing) {
50232                 var ln = values.length,
50233                     out = [],
50234                     i, val, parsedString, from, delta;
50235                 for (i = 0; i < ln; i++) {
50236                     val = values[i][1];
50237                     if (val) {
50238                         from = val.from;
50239                         delta = val.delta;
50240                         //multiple checks to reformat the color if it can't recognized by computeDelta.
50241                         val = (typeof val == 'object' && 'red' in val)? 
50242                                 'rgb(' + val.red + ', ' + val.green + ', ' + val.blue + ')' : val;
50243                         val = (typeof val == 'object' && val.length)? val[0] : val;
50244                         if (typeof val == 'undefined') {
50245                             return [];
50246                         }
50247                         parsedString = typeof val == 'string'? val :
50248                             'rgb(' + [
50249                                   (from.red + Math.round(delta.red * easing)) % 256,
50250                                   (from.green + Math.round(delta.green * easing)) % 256,
50251                                   (from.blue + Math.round(delta.blue * easing)) % 256
50252                               ].join(',') + ')';
50253                         out.push([
50254                             values[i][0],
50255                             parsedString
50256                         ]);
50257                     }
50258                 }
50259                 return out;
50260             }
50261         },
50262         object: {
50263             interpolate: function(prop, damper) {
50264                 damper = (typeof damper == 'number') ? damper : 1;
50265                 var out = {},
50266                     p;
50267                 for(p in prop) {
50268                     out[p] = parseInt(prop[p], 10) * damper;
50269                 }
50270                 return out;
50271             },
50272
50273             computeDelta: function(from, end, damper, initial) {
50274                 from = this.interpolate(from);
50275                 end = this.interpolate(end, damper);
50276                 var start = initial ? initial : from,
50277                     delta = {},
50278                     p;
50279
50280                 for(p in end) {
50281                     delta[p] = end[p] - start[p];
50282                 }
50283                 return {
50284                     from:  from,
50285                     delta: delta
50286                 };
50287             },
50288
50289             get: function(start, end, damper, initialFrom) {
50290                 var ln = start.length,
50291                     out = [],
50292                     i, initial;
50293                 for (i = 0; i < ln; i++) {
50294                     if (initialFrom) {
50295                         initial = initialFrom[i][1].from;
50296                     }
50297                     out.push([start[i][0], this.computeDelta(start[i][1], end, damper, initial)]);
50298                 }
50299                 return out;
50300             },
50301
50302             set: function(values, easing) {
50303                 var ln = values.length,
50304                     out = [],
50305                     outObject = {},
50306                     i, from, delta, val, p;
50307                 for (i = 0; i < ln; i++) {
50308                     val  = values[i][1];
50309                     from = val.from;
50310                     delta = val.delta;
50311                     for (p in from) {
50312                         outObject[p] = Math.round(from[p] + delta[p] * easing);
50313                     }
50314                     out.push([
50315                         values[i][0],
50316                         outObject
50317                     ]);
50318                 }
50319                 return out;
50320             }
50321         },
50322
50323         path: {
50324             computeDelta: function(from, end, damper, initial) {
50325                 damper = (typeof damper == 'number') ? damper : 1;
50326                 var start;
50327                 from = +from || 0;
50328                 end = +end || 0;
50329                 start = (initial != null) ? initial : from;
50330                 return {
50331                     from: from,
50332                     delta: (end - start) * damper
50333                 };
50334             },
50335
50336             forcePath: function(path) {
50337                 if (!Ext.isArray(path) && !Ext.isArray(path[0])) {
50338                     path = Ext.draw.Draw.parsePathString(path);
50339                 }
50340                 return path;
50341             },
50342
50343             get: function(start, end, damper, initialFrom) {
50344                 var endPath = this.forcePath(end),
50345                     out = [],
50346                     startLn = start.length,
50347                     startPathLn, pointsLn, i, deltaPath, initial, j, k, path, startPath;
50348                 for (i = 0; i < startLn; i++) {
50349                     startPath = this.forcePath(start[i][1]);
50350
50351                     deltaPath = Ext.draw.Draw.interpolatePaths(startPath, endPath);
50352                     startPath = deltaPath[0];
50353                     endPath = deltaPath[1];
50354
50355                     startPathLn = startPath.length;
50356                     path = [];
50357                     for (j = 0; j < startPathLn; j++) {
50358                         deltaPath = [startPath[j][0]];
50359                         pointsLn = startPath[j].length;
50360                         for (k = 1; k < pointsLn; k++) {
50361                             initial = initialFrom && initialFrom[0][1][j][k].from;
50362                             deltaPath.push(this.computeDelta(startPath[j][k], endPath[j][k], damper, initial));
50363                         }
50364                         path.push(deltaPath);
50365                     }
50366                     out.push([start[i][0], path]);
50367                 }
50368                 return out;
50369             },
50370
50371             set: function(values, easing) {
50372                 var ln = values.length,
50373                     out = [],
50374                     i, j, k, newPath, calcPath, deltaPath, deltaPathLn, pointsLn;
50375                 for (i = 0; i < ln; i++) {
50376                     deltaPath = values[i][1];
50377                     newPath = [];
50378                     deltaPathLn = deltaPath.length;
50379                     for (j = 0; j < deltaPathLn; j++) {
50380                         calcPath = [deltaPath[j][0]];
50381                         pointsLn = deltaPath[j].length;
50382                         for (k = 1; k < pointsLn; k++) {
50383                             calcPath.push(deltaPath[j][k].from + deltaPath[j][k].delta * easing);
50384                         }
50385                         newPath.push(calcPath.join(','));
50386                     }
50387                     out.push([values[i][0], newPath.join(',')]);
50388                 }
50389                 return out;
50390             }
50391         }
50392         /* End Definitions */
50393     }
50394 }, function() {
50395     Ext.each([
50396         'outlineColor',
50397         'backgroundColor',
50398         'borderColor',
50399         'borderTopColor',
50400         'borderRightColor', 
50401         'borderBottomColor', 
50402         'borderLeftColor',
50403         'fill',
50404         'stroke'
50405     ], function(prop) {
50406         this[prop] = this.color;
50407     }, this);
50408 });
50409 /**
50410  * @class Ext.fx.Anim
50411  * 
50412  * This class manages animation for a specific {@link #target}. The animation allows
50413  * animation of various properties on the target, such as size, position, color and others.
50414  * 
50415  * ## Starting Conditions
50416  * The starting conditions for the animation are provided by the {@link #from} configuration.
50417  * Any/all of the properties in the {@link #from} configuration can be specified. If a particular
50418  * property is not defined, the starting value for that property will be read directly from the target.
50419  * 
50420  * ## End Conditions
50421  * The ending conditions for the animation are provided by the {@link #to} configuration. These mark
50422  * the final values once the animations has finished. The values in the {@link #from} can mirror
50423  * those in the {@link #to} configuration to provide a starting point.
50424  * 
50425  * ## Other Options
50426  *  - {@link #duration}: Specifies the time period of the animation.
50427  *  - {@link #easing}: Specifies the easing of the animation.
50428  *  - {@link #iterations}: Allows the animation to repeat a number of times.
50429  *  - {@link #alternate}: Used in conjunction with {@link #iterations}, reverses the direction every second iteration.
50430  * 
50431  * ## Example Code
50432  * 
50433  *     var myComponent = Ext.create('Ext.Component', {
50434  *         renderTo: document.body,
50435  *         width: 200,
50436  *         height: 200,
50437  *         style: 'border: 1px solid red;'
50438  *     });
50439  *     
50440  *     new Ext.fx.Anim({
50441  *         target: myComponent,
50442  *         duration: 1000,
50443  *         from: {
50444  *             width: 400 //starting width 400
50445  *         },
50446  *         to: {
50447  *             width: 300, //end width 300
50448  *             height: 300 // end width 300
50449  *         }
50450  *     });
50451  */
50452 Ext.define('Ext.fx.Anim', {
50453
50454     /* Begin Definitions */
50455
50456     mixins: {
50457         observable: 'Ext.util.Observable'
50458     },
50459
50460     requires: ['Ext.fx.Manager', 'Ext.fx.Animator', 'Ext.fx.Easing', 'Ext.fx.CubicBezier', 'Ext.fx.PropertyHandler'],
50461
50462     /* End Definitions */
50463
50464     isAnimation: true,
50465     /**
50466      * @cfg {Number} duration
50467      * Time in milliseconds for a single animation to last. Defaults to 250. If the {@link #iterations} property is
50468      * specified, then each animate will take the same duration for each iteration.
50469      */
50470     duration: 250,
50471
50472     /**
50473      * @cfg {Number} delay
50474      * Time to delay before starting the animation. Defaults to 0.
50475      */
50476     delay: 0,
50477
50478     /* private used to track a delayed starting time */
50479     delayStart: 0,
50480
50481     /**
50482      * @cfg {Boolean} dynamic
50483      * 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.
50484      */
50485     dynamic: false,
50486
50487     /**
50488      * @cfg {String} easing
50489 This describes how the intermediate values used during a transition will be calculated. It allows for a transition to change
50490 speed over its duration. 
50491
50492          -backIn
50493          -backOut
50494          -bounceIn
50495          -bounceOut
50496          -ease
50497          -easeIn
50498          -easeOut
50499          -easeInOut
50500          -elasticIn
50501          -elasticOut
50502          -cubic-bezier(x1, y1, x2, y2)
50503
50504 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
50505 as (x1, y1, x2, y2). All values must be in the range [0, 1] or the definition is invalid.
50506      * @markdown
50507      */
50508     easing: 'ease',
50509
50510      /**
50511       * @cfg {Object} keyframes
50512       * Animation keyframes follow the CSS3 Animation configuration pattern. 'from' is always considered '0%' and 'to'
50513       * is considered '100%'.<b>Every keyframe declaration must have a keyframe rule for 0% and 100%, possibly defined using
50514       * "from" or "to"</b>.  A keyframe declaration without these keyframe selectors is invalid and will not be available for
50515       * animation.  The keyframe declaration for a keyframe rule consists of properties and values. Properties that are unable to
50516       * be animated are ignored in these rules, with the exception of 'easing' which can be changed at each keyframe. For example:
50517  <pre><code>
50518 keyframes : {
50519     '0%': {
50520         left: 100
50521     },
50522     '40%': {
50523         left: 150
50524     },
50525     '60%': {
50526         left: 75
50527     },
50528     '100%': {
50529         left: 100
50530     }
50531 }
50532  </code></pre>
50533       */
50534
50535     /**
50536      * @private
50537      */
50538     damper: 1,
50539
50540     /**
50541      * @private
50542      */
50543     bezierRE: /^(?:cubic-)?bezier\(([^,]+),([^,]+),([^,]+),([^\)]+)\)/,
50544
50545     /**
50546      * Run the animation from the end to the beginning
50547      * Defaults to false.
50548      * @cfg {Boolean} reverse
50549      */
50550     reverse: false,
50551
50552     /**
50553      * Flag to determine if the animation has started
50554      * @property running
50555      * @type boolean
50556      */
50557     running: false,
50558
50559     /**
50560      * Flag to determine if the animation is paused. Only set this to true if you need to
50561      * keep the Anim instance around to be unpaused later; otherwise call {@link #end}.
50562      * @property paused
50563      * @type boolean
50564      */
50565     paused: false,
50566
50567     /**
50568      * Number of times to execute the animation. Defaults to 1.
50569      * @cfg {int} iterations
50570      */
50571     iterations: 1,
50572
50573     /**
50574      * Used in conjunction with iterations to reverse the animation each time an iteration completes.
50575      * @cfg {Boolean} alternate
50576      * Defaults to false.
50577      */
50578     alternate: false,
50579
50580     /**
50581      * Current iteration the animation is running.
50582      * @property currentIteration
50583      * @type int
50584      */
50585     currentIteration: 0,
50586
50587     /**
50588      * Starting time of the animation.
50589      * @property startTime
50590      * @type Date
50591      */
50592     startTime: 0,
50593
50594     /**
50595      * Contains a cache of the interpolators to be used.
50596      * @private
50597      * @property propHandlers
50598      * @type Object
50599      */
50600
50601     /**
50602      * @cfg {String/Object} target
50603      * The {@link Ext.fx.target.Target} to apply the animation to.  This should only be specified when creating an Ext.fx.Anim directly.
50604      * The target does not need to be a {@link Ext.fx.target.Target} instance, it can be the underlying object. For example, you can
50605      * pass a Component, Element or Sprite as the target and the Anim will create the appropriate {@link Ext.fx.target.Target} object
50606      * automatically.
50607      */
50608
50609     /**
50610      * @cfg {Object} from
50611      * An object containing property/value pairs for the beginning of the animation.  If not specified, the current state of the
50612      * Ext.fx.target will be used. For example:
50613 <pre><code>
50614 from : {
50615     opacity: 0,       // Transparent
50616     color: '#ffffff', // White
50617     left: 0
50618 }
50619 </code></pre>
50620      */
50621
50622     /**
50623      * @cfg {Object} to
50624      * An object containing property/value pairs for the end of the animation. For example:
50625  <pre><code>
50626  to : {
50627      opacity: 1,       // Opaque
50628      color: '#00ff00', // Green
50629      left: 500
50630  }
50631  </code></pre>
50632      */
50633
50634     // @private
50635     constructor: function(config) {
50636         var me = this;
50637         config = config || {};
50638         // If keyframes are passed, they really want an Animator instead.
50639         if (config.keyframes) {
50640             return Ext.create('Ext.fx.Animator', config);
50641         }
50642         config = Ext.apply(me, config);
50643         if (me.from === undefined) {
50644             me.from = {};
50645         }
50646         me.propHandlers = {};
50647         me.config = config;
50648         me.target = Ext.fx.Manager.createTarget(me.target);
50649         me.easingFn = Ext.fx.Easing[me.easing];
50650         me.target.dynamic = me.dynamic;
50651
50652         // If not a pre-defined curve, try a cubic-bezier
50653         if (!me.easingFn) {
50654             me.easingFn = String(me.easing).match(me.bezierRE);
50655             if (me.easingFn && me.easingFn.length == 5) {
50656                 var curve = me.easingFn;
50657                 me.easingFn = Ext.fx.cubicBezier(+curve[1], +curve[2], +curve[3], +curve[4]);
50658             }
50659         }
50660         me.id = Ext.id(null, 'ext-anim-');
50661         Ext.fx.Manager.addAnim(me);
50662         me.addEvents(
50663             /**
50664              * @event beforeanimate
50665              * Fires before the animation starts. A handler can return false to cancel the animation.
50666              * @param {Ext.fx.Anim} this
50667              */
50668             'beforeanimate',
50669              /**
50670               * @event afteranimate
50671               * Fires when the animation is complete.
50672               * @param {Ext.fx.Anim} this
50673               * @param {Date} startTime
50674               */
50675             'afteranimate',
50676              /**
50677               * @event lastframe
50678               * Fires when the animation's last frame has been set.
50679               * @param {Ext.fx.Anim} this
50680               * @param {Date} startTime
50681               */
50682             'lastframe'
50683         );
50684         me.mixins.observable.constructor.call(me, config);
50685         if (config.callback) {
50686             me.on('afteranimate', config.callback, config.scope);
50687         }
50688         return me;
50689     },
50690
50691     /**
50692      * @private
50693      * Helper to the target
50694      */
50695     setAttr: function(attr, value) {
50696         return Ext.fx.Manager.items.get(this.id).setAttr(this.target, attr, value);
50697     },
50698
50699     /*
50700      * @private
50701      * Set up the initial currentAttrs hash.
50702      */
50703     initAttrs: function() {
50704         var me = this,
50705             from = me.from,
50706             to = me.to,
50707             initialFrom = me.initialFrom || {},
50708             out = {},
50709             start, end, propHandler, attr;
50710
50711         for (attr in to) {
50712             if (to.hasOwnProperty(attr)) {
50713                 start = me.target.getAttr(attr, from[attr]);
50714                 end = to[attr];
50715                 // Use default (numeric) property handler
50716                 if (!Ext.fx.PropertyHandler[attr]) {
50717                     if (Ext.isObject(end)) {
50718                         propHandler = me.propHandlers[attr] = Ext.fx.PropertyHandler.object;
50719                     } else {
50720                         propHandler = me.propHandlers[attr] = Ext.fx.PropertyHandler.defaultHandler;
50721                     }
50722                 }
50723                 // Use custom handler
50724                 else {
50725                     propHandler = me.propHandlers[attr] = Ext.fx.PropertyHandler[attr];
50726                 }
50727                 out[attr] = propHandler.get(start, end, me.damper, initialFrom[attr], attr);
50728             }
50729         }
50730         me.currentAttrs = out;
50731     },
50732
50733     /*
50734      * @private
50735      * Fires beforeanimate and sets the running flag.
50736      */
50737     start: function(startTime) {
50738         var me = this,
50739             delay = me.delay,
50740             delayStart = me.delayStart,
50741             delayDelta;
50742         if (delay) {
50743             if (!delayStart) {
50744                 me.delayStart = startTime;
50745                 return;
50746             }
50747             else {
50748                 delayDelta = startTime - delayStart;
50749                 if (delayDelta < delay) {
50750                     return;
50751                 }
50752                 else {
50753                     // Compensate for frame delay;
50754                     startTime = new Date(delayStart.getTime() + delay);
50755                 }
50756             }
50757         }
50758         if (me.fireEvent('beforeanimate', me) !== false) {
50759             me.startTime = startTime;
50760             if (!me.paused && !me.currentAttrs) {
50761                 me.initAttrs();
50762             }
50763             me.running = true;
50764         }
50765     },
50766
50767     /*
50768      * @private
50769      * Calculate attribute value at the passed timestamp.
50770      * @returns a hash of the new attributes.
50771      */
50772     runAnim: function(elapsedTime) {
50773         var me = this,
50774             attrs = me.currentAttrs,
50775             duration = me.duration,
50776             easingFn = me.easingFn,
50777             propHandlers = me.propHandlers,
50778             ret = {},
50779             easing, values, attr, lastFrame;
50780
50781         if (elapsedTime >= duration) {
50782             elapsedTime = duration;
50783             lastFrame = true;
50784         }
50785         if (me.reverse) {
50786             elapsedTime = duration - elapsedTime;
50787         }
50788
50789         for (attr in attrs) {
50790             if (attrs.hasOwnProperty(attr)) {
50791                 values = attrs[attr];
50792                 easing = lastFrame ? 1 : easingFn(elapsedTime / duration);
50793                 ret[attr] = propHandlers[attr].set(values, easing);
50794             }
50795         }
50796         return ret;
50797     },
50798
50799     /*
50800      * @private
50801      * Perform lastFrame cleanup and handle iterations
50802      * @returns a hash of the new attributes.
50803      */
50804     lastFrame: function() {
50805         var me = this,
50806             iter = me.iterations,
50807             iterCount = me.currentIteration;
50808
50809         iterCount++;
50810         if (iterCount < iter) {
50811             if (me.alternate) {
50812                 me.reverse = !me.reverse;
50813             }
50814             me.startTime = new Date();
50815             me.currentIteration = iterCount;
50816             // Turn off paused for CSS3 Transitions
50817             me.paused = false;
50818         }
50819         else {
50820             me.currentIteration = 0;
50821             me.end();
50822             me.fireEvent('lastframe', me, me.startTime);
50823         }
50824     },
50825
50826     /*
50827      * Fire afteranimate event and end the animation. Usually called automatically when the
50828      * animation reaches its final frame, but can also be called manually to pre-emptively
50829      * stop and destroy the running animation.
50830      */
50831     end: function() {
50832         var me = this;
50833         me.startTime = 0;
50834         me.paused = false;
50835         me.running = false;
50836         Ext.fx.Manager.removeAnim(me);
50837         me.fireEvent('afteranimate', me, me.startTime);
50838     }
50839 });
50840 // Set flag to indicate that Fx is available. Class might not be available immediately.
50841 Ext.enableFx = true;
50842
50843 /*
50844  * This is a derivative of the similarly named class in the YUI Library.
50845  * The original license:
50846  * Copyright (c) 2006, Yahoo! Inc. All rights reserved.
50847  * Code licensed under the BSD License:
50848  * http://developer.yahoo.net/yui/license.txt
50849  */
50850
50851
50852 /**
50853  * @class Ext.dd.DragDrop
50854  * Defines the interface and base operation of items that that can be
50855  * dragged or can be drop targets.  It was designed to be extended, overriding
50856  * the event handlers for startDrag, onDrag, onDragOver and onDragOut.
50857  * Up to three html elements can be associated with a DragDrop instance:
50858  * <ul>
50859  * <li>linked element: the element that is passed into the constructor.
50860  * This is the element which defines the boundaries for interaction with
50861  * other DragDrop objects.</li>
50862  * <li>handle element(s): The drag operation only occurs if the element that
50863  * was clicked matches a handle element.  By default this is the linked
50864  * element, but there are times that you will want only a portion of the
50865  * linked element to initiate the drag operation, and the setHandleElId()
50866  * method provides a way to define this.</li>
50867  * <li>drag element: this represents the element that would be moved along
50868  * with the cursor during a drag operation.  By default, this is the linked
50869  * element itself as in {@link Ext.dd.DD}.  setDragElId() lets you define
50870  * a separate element that would be moved, as in {@link Ext.dd.DDProxy}.
50871  * </li>
50872  * </ul>
50873  * This class should not be instantiated until the onload event to ensure that
50874  * the associated elements are available.
50875  * The following would define a DragDrop obj that would interact with any
50876  * other DragDrop obj in the "group1" group:
50877  * <pre>
50878  *  dd = new Ext.dd.DragDrop("div1", "group1");
50879  * </pre>
50880  * Since none of the event handlers have been implemented, nothing would
50881  * actually happen if you were to run the code above.  Normally you would
50882  * override this class or one of the default implementations, but you can
50883  * also override the methods you want on an instance of the class...
50884  * <pre>
50885  *  dd.onDragDrop = function(e, id) {
50886  *  &nbsp;&nbsp;alert("dd was dropped on " + id);
50887  *  }
50888  * </pre>
50889  * @constructor
50890  * @param {String} id of the element that is linked to this instance
50891  * @param {String} sGroup the group of related DragDrop objects
50892  * @param {object} config an object containing configurable attributes
50893  *                Valid properties for DragDrop:
50894  *                    padding, isTarget, maintainOffset, primaryButtonOnly
50895  */
50896
50897 Ext.define('Ext.dd.DragDrop', {
50898     requires: ['Ext.dd.DragDropManager'],
50899     constructor: function(id, sGroup, config) {
50900         if(id) {
50901             this.init(id, sGroup, config);
50902         }
50903     },
50904     
50905     /**
50906      * Set to false to enable a DragDrop object to fire drag events while dragging
50907      * over its own Element. Defaults to true - DragDrop objects do not by default
50908      * fire drag events to themselves.
50909      * @property ignoreSelf
50910      * @type Boolean
50911      */
50912
50913     /**
50914      * The id of the element associated with this object.  This is what we
50915      * refer to as the "linked element" because the size and position of
50916      * this element is used to determine when the drag and drop objects have
50917      * interacted.
50918      * @property id
50919      * @type String
50920      */
50921     id: null,
50922
50923     /**
50924      * Configuration attributes passed into the constructor
50925      * @property config
50926      * @type object
50927      */
50928     config: null,
50929
50930     /**
50931      * The id of the element that will be dragged.  By default this is same
50932      * as the linked element, but could be changed to another element. Ex:
50933      * Ext.dd.DDProxy
50934      * @property dragElId
50935      * @type String
50936      * @private
50937      */
50938     dragElId: null,
50939
50940     /**
50941      * The ID of the element that initiates the drag operation.  By default
50942      * this is the linked element, but could be changed to be a child of this
50943      * element.  This lets us do things like only starting the drag when the
50944      * header element within the linked html element is clicked.
50945      * @property handleElId
50946      * @type String
50947      * @private
50948      */
50949     handleElId: null,
50950
50951     /**
50952      * An object who's property names identify HTML tags to be considered invalid as drag handles.
50953      * A non-null property value identifies the tag as invalid. Defaults to the 
50954      * following value which prevents drag operations from being initiated by &lt;a> elements:<pre><code>
50955 {
50956     A: "A"
50957 }</code></pre>
50958      * @property invalidHandleTypes
50959      * @type Object
50960      */
50961     invalidHandleTypes: null,
50962
50963     /**
50964      * An object who's property names identify the IDs of elements to be considered invalid as drag handles.
50965      * A non-null property value identifies the ID as invalid. For example, to prevent
50966      * dragging from being initiated on element ID "foo", use:<pre><code>
50967 {
50968     foo: true
50969 }</code></pre>
50970      * @property invalidHandleIds
50971      * @type Object
50972      */
50973     invalidHandleIds: null,
50974
50975     /**
50976      * An Array of CSS class names for elements to be considered in valid as drag handles.
50977      * @property invalidHandleClasses
50978      * @type Array
50979      */
50980     invalidHandleClasses: null,
50981
50982     /**
50983      * The linked element's absolute X position at the time the drag was
50984      * started
50985      * @property startPageX
50986      * @type int
50987      * @private
50988      */
50989     startPageX: 0,
50990
50991     /**
50992      * The linked element's absolute X position at the time the drag was
50993      * started
50994      * @property startPageY
50995      * @type int
50996      * @private
50997      */
50998     startPageY: 0,
50999
51000     /**
51001      * The group defines a logical collection of DragDrop objects that are
51002      * related.  Instances only get events when interacting with other
51003      * DragDrop object in the same group.  This lets us define multiple
51004      * groups using a single DragDrop subclass if we want.
51005      * @property groups
51006      * @type object An object in the format {'group1':true, 'group2':true}
51007      */
51008     groups: null,
51009
51010     /**
51011      * Individual drag/drop instances can be locked.  This will prevent
51012      * onmousedown start drag.
51013      * @property locked
51014      * @type boolean
51015      * @private
51016      */
51017     locked: false,
51018
51019     /**
51020      * Lock this instance
51021      * @method lock
51022      */
51023     lock: function() {
51024         this.locked = true;
51025     },
51026
51027     /**
51028      * When set to true, other DD objects in cooperating DDGroups do not receive
51029      * notification events when this DD object is dragged over them. Defaults to false.
51030      * @property moveOnly
51031      * @type boolean
51032      */
51033     moveOnly: false,
51034
51035     /**
51036      * Unlock this instace
51037      * @method unlock
51038      */
51039     unlock: function() {
51040         this.locked = false;
51041     },
51042
51043     /**
51044      * By default, all instances can be a drop target.  This can be disabled by
51045      * setting isTarget to false.
51046      * @property isTarget
51047      * @type boolean
51048      */
51049     isTarget: true,
51050
51051     /**
51052      * The padding configured for this drag and drop object for calculating
51053      * the drop zone intersection with this object.
51054      * @property padding
51055      * @type int[] An array containing the 4 padding values: [top, right, bottom, left]
51056      */
51057     padding: null,
51058
51059     /**
51060      * Cached reference to the linked element
51061      * @property _domRef
51062      * @private
51063      */
51064     _domRef: null,
51065
51066     /**
51067      * Internal typeof flag
51068      * @property __ygDragDrop
51069      * @private
51070      */
51071     __ygDragDrop: true,
51072
51073     /**
51074      * Set to true when horizontal contraints are applied
51075      * @property constrainX
51076      * @type boolean
51077      * @private
51078      */
51079     constrainX: false,
51080
51081     /**
51082      * Set to true when vertical contraints are applied
51083      * @property constrainY
51084      * @type boolean
51085      * @private
51086      */
51087     constrainY: false,
51088
51089     /**
51090      * The left constraint
51091      * @property minX
51092      * @type int
51093      * @private
51094      */
51095     minX: 0,
51096
51097     /**
51098      * The right constraint
51099      * @property maxX
51100      * @type int
51101      * @private
51102      */
51103     maxX: 0,
51104
51105     /**
51106      * The up constraint
51107      * @property minY
51108      * @type int
51109      * @private
51110      */
51111     minY: 0,
51112
51113     /**
51114      * The down constraint
51115      * @property maxY
51116      * @type int
51117      * @private
51118      */
51119     maxY: 0,
51120
51121     /**
51122      * Maintain offsets when we resetconstraints.  Set to true when you want
51123      * the position of the element relative to its parent to stay the same
51124      * when the page changes
51125      *
51126      * @property maintainOffset
51127      * @type boolean
51128      */
51129     maintainOffset: false,
51130
51131     /**
51132      * Array of pixel locations the element will snap to if we specified a
51133      * horizontal graduation/interval.  This array is generated automatically
51134      * when you define a tick interval.
51135      * @property xTicks
51136      * @type int[]
51137      */
51138     xTicks: null,
51139
51140     /**
51141      * Array of pixel locations the element will snap to if we specified a
51142      * vertical graduation/interval.  This array is generated automatically
51143      * when you define a tick interval.
51144      * @property yTicks
51145      * @type int[]
51146      */
51147     yTicks: null,
51148
51149     /**
51150      * By default the drag and drop instance will only respond to the primary
51151      * button click (left button for a right-handed mouse).  Set to true to
51152      * allow drag and drop to start with any mouse click that is propogated
51153      * by the browser
51154      * @property primaryButtonOnly
51155      * @type boolean
51156      */
51157     primaryButtonOnly: true,
51158
51159     /**
51160      * The available property is false until the linked dom element is accessible.
51161      * @property available
51162      * @type boolean
51163      */
51164     available: false,
51165
51166     /**
51167      * By default, drags can only be initiated if the mousedown occurs in the
51168      * region the linked element is.  This is done in part to work around a
51169      * bug in some browsers that mis-report the mousedown if the previous
51170      * mouseup happened outside of the window.  This property is set to true
51171      * if outer handles are defined.
51172      *
51173      * @property hasOuterHandles
51174      * @type boolean
51175      * @default false
51176      */
51177     hasOuterHandles: false,
51178
51179     /**
51180      * Code that executes immediately before the startDrag event
51181      * @method b4StartDrag
51182      * @private
51183      */
51184     b4StartDrag: function(x, y) { },
51185
51186     /**
51187      * Abstract method called after a drag/drop object is clicked
51188      * and the drag or mousedown time thresholds have beeen met.
51189      * @method startDrag
51190      * @param {int} X click location
51191      * @param {int} Y click location
51192      */
51193     startDrag: function(x, y) { /* override this */ },
51194
51195     /**
51196      * Code that executes immediately before the onDrag event
51197      * @method b4Drag
51198      * @private
51199      */
51200     b4Drag: function(e) { },
51201
51202     /**
51203      * Abstract method called during the onMouseMove event while dragging an
51204      * object.
51205      * @method onDrag
51206      * @param {Event} e the mousemove event
51207      */
51208     onDrag: function(e) { /* override this */ },
51209
51210     /**
51211      * Abstract method called when this element fist begins hovering over
51212      * another DragDrop obj
51213      * @method onDragEnter
51214      * @param {Event} e the mousemove event
51215      * @param {String|DragDrop[]} id In POINT mode, the element
51216      * id this is hovering over.  In INTERSECT mode, an array of one or more
51217      * dragdrop items being hovered over.
51218      */
51219     onDragEnter: function(e, id) { /* override this */ },
51220
51221     /**
51222      * Code that executes immediately before the onDragOver event
51223      * @method b4DragOver
51224      * @private
51225      */
51226     b4DragOver: function(e) { },
51227
51228     /**
51229      * Abstract method called when this element is hovering over another
51230      * DragDrop obj
51231      * @method onDragOver
51232      * @param {Event} e the mousemove event
51233      * @param {String|DragDrop[]} id In POINT mode, the element
51234      * id this is hovering over.  In INTERSECT mode, an array of dd items
51235      * being hovered over.
51236      */
51237     onDragOver: function(e, id) { /* override this */ },
51238
51239     /**
51240      * Code that executes immediately before the onDragOut event
51241      * @method b4DragOut
51242      * @private
51243      */
51244     b4DragOut: function(e) { },
51245
51246     /**
51247      * Abstract method called when we are no longer hovering over an element
51248      * @method onDragOut
51249      * @param {Event} e the mousemove event
51250      * @param {String|DragDrop[]} id In POINT mode, the element
51251      * id this was hovering over.  In INTERSECT mode, an array of dd items
51252      * that the mouse is no longer over.
51253      */
51254     onDragOut: function(e, id) { /* override this */ },
51255
51256     /**
51257      * Code that executes immediately before the onDragDrop event
51258      * @method b4DragDrop
51259      * @private
51260      */
51261     b4DragDrop: function(e) { },
51262
51263     /**
51264      * Abstract method called when this item is dropped on another DragDrop
51265      * obj
51266      * @method onDragDrop
51267      * @param {Event} e the mouseup event
51268      * @param {String|DragDrop[]} id In POINT mode, the element
51269      * id this was dropped on.  In INTERSECT mode, an array of dd items this
51270      * was dropped on.
51271      */
51272     onDragDrop: function(e, id) { /* override this */ },
51273
51274     /**
51275      * Abstract method called when this item is dropped on an area with no
51276      * drop target
51277      * @method onInvalidDrop
51278      * @param {Event} e the mouseup event
51279      */
51280     onInvalidDrop: function(e) { /* override this */ },
51281
51282     /**
51283      * Code that executes immediately before the endDrag event
51284      * @method b4EndDrag
51285      * @private
51286      */
51287     b4EndDrag: function(e) { },
51288
51289     /**
51290      * Fired when we are done dragging the object
51291      * @method endDrag
51292      * @param {Event} e the mouseup event
51293      */
51294     endDrag: function(e) { /* override this */ },
51295
51296     /**
51297      * Code executed immediately before the onMouseDown event
51298      * @method b4MouseDown
51299      * @param {Event} e the mousedown event
51300      * @private
51301      */
51302     b4MouseDown: function(e) {  },
51303
51304     /**
51305      * Event handler that fires when a drag/drop obj gets a mousedown
51306      * @method onMouseDown
51307      * @param {Event} e the mousedown event
51308      */
51309     onMouseDown: function(e) { /* override this */ },
51310
51311     /**
51312      * Event handler that fires when a drag/drop obj gets a mouseup
51313      * @method onMouseUp
51314      * @param {Event} e the mouseup event
51315      */
51316     onMouseUp: function(e) { /* override this */ },
51317
51318     /**
51319      * Override the onAvailable method to do what is needed after the initial
51320      * position was determined.
51321      * @method onAvailable
51322      */
51323     onAvailable: function () {
51324     },
51325
51326     /**
51327      * Provides default constraint padding to "constrainTo" elements (defaults to {left: 0, right:0, top:0, bottom:0}).
51328      * @type Object
51329      */
51330     defaultPadding: {
51331         left: 0,
51332         right: 0,
51333         top: 0,
51334         bottom: 0
51335     },
51336
51337     /**
51338      * Initializes the drag drop object's constraints to restrict movement to a certain element.
51339  *
51340  * Usage:
51341  <pre><code>
51342  var dd = new Ext.dd.DDProxy("dragDiv1", "proxytest",
51343                 { dragElId: "existingProxyDiv" });
51344  dd.startDrag = function(){
51345      this.constrainTo("parent-id");
51346  };
51347  </code></pre>
51348  * Or you can initalize it using the {@link Ext.core.Element} object:
51349  <pre><code>
51350  Ext.get("dragDiv1").initDDProxy("proxytest", {dragElId: "existingProxyDiv"}, {
51351      startDrag : function(){
51352          this.constrainTo("parent-id");
51353      }
51354  });
51355  </code></pre>
51356      * @param {Mixed} constrainTo The element to constrain to.
51357      * @param {Object/Number} pad (optional) Pad provides a way to specify "padding" of the constraints,
51358      * and can be either a number for symmetrical padding (4 would be equal to {left:4, right:4, top:4, bottom:4}) or
51359      * an object containing the sides to pad. For example: {right:10, bottom:10}
51360      * @param {Boolean} inContent (optional) Constrain the draggable in the content box of the element (inside padding and borders)
51361      */
51362     constrainTo : function(constrainTo, pad, inContent){
51363         if(Ext.isNumber(pad)){
51364             pad = {left: pad, right:pad, top:pad, bottom:pad};
51365         }
51366         pad = pad || this.defaultPadding;
51367         var b = Ext.get(this.getEl()).getBox(),
51368             ce = Ext.get(constrainTo),
51369             s = ce.getScroll(),
51370             c, 
51371             cd = ce.dom;
51372         if(cd == document.body){
51373             c = { x: s.left, y: s.top, width: Ext.core.Element.getViewWidth(), height: Ext.core.Element.getViewHeight()};
51374         }else{
51375             var xy = ce.getXY();
51376             c = {x : xy[0], y: xy[1], width: cd.clientWidth, height: cd.clientHeight};
51377         }
51378
51379
51380         var topSpace = b.y - c.y,
51381             leftSpace = b.x - c.x;
51382
51383         this.resetConstraints();
51384         this.setXConstraint(leftSpace - (pad.left||0), // left
51385                 c.width - leftSpace - b.width - (pad.right||0), //right
51386                                 this.xTickSize
51387         );
51388         this.setYConstraint(topSpace - (pad.top||0), //top
51389                 c.height - topSpace - b.height - (pad.bottom||0), //bottom
51390                                 this.yTickSize
51391         );
51392     },
51393
51394     /**
51395      * Returns a reference to the linked element
51396      * @method getEl
51397      * @return {HTMLElement} the html element
51398      */
51399     getEl: function() {
51400         if (!this._domRef) {
51401             this._domRef = Ext.getDom(this.id);
51402         }
51403
51404         return this._domRef;
51405     },
51406
51407     /**
51408      * Returns a reference to the actual element to drag.  By default this is
51409      * the same as the html element, but it can be assigned to another
51410      * element. An example of this can be found in Ext.dd.DDProxy
51411      * @method getDragEl
51412      * @return {HTMLElement} the html element
51413      */
51414     getDragEl: function() {
51415         return Ext.getDom(this.dragElId);
51416     },
51417
51418     /**
51419      * Sets up the DragDrop object.  Must be called in the constructor of any
51420      * Ext.dd.DragDrop subclass
51421      * @method init
51422      * @param id the id of the linked element
51423      * @param {String} sGroup the group of related items
51424      * @param {object} config configuration attributes
51425      */
51426     init: function(id, sGroup, config) {
51427         this.initTarget(id, sGroup, config);
51428         Ext.EventManager.on(this.id, "mousedown", this.handleMouseDown, this);
51429         // Ext.EventManager.on(this.id, "selectstart", Event.preventDefault);
51430     },
51431
51432     /**
51433      * Initializes Targeting functionality only... the object does not
51434      * get a mousedown handler.
51435      * @method initTarget
51436      * @param id the id of the linked element
51437      * @param {String} sGroup the group of related items
51438      * @param {object} config configuration attributes
51439      */
51440     initTarget: function(id, sGroup, config) {
51441
51442         // configuration attributes
51443         this.config = config || {};
51444
51445         // create a local reference to the drag and drop manager
51446         this.DDMInstance = Ext.dd.DragDropManager;
51447         // initialize the groups array
51448         this.groups = {};
51449
51450         // assume that we have an element reference instead of an id if the
51451         // parameter is not a string
51452         if (typeof id !== "string") {
51453             id = Ext.id(id);
51454         }
51455
51456         // set the id
51457         this.id = id;
51458
51459         // add to an interaction group
51460         this.addToGroup((sGroup) ? sGroup : "default");
51461
51462         // We don't want to register this as the handle with the manager
51463         // so we just set the id rather than calling the setter.
51464         this.handleElId = id;
51465
51466         // the linked element is the element that gets dragged by default
51467         this.setDragElId(id);
51468
51469         // by default, clicked anchors will not start drag operations.
51470         this.invalidHandleTypes = { A: "A" };
51471         this.invalidHandleIds = {};
51472         this.invalidHandleClasses = [];
51473
51474         this.applyConfig();
51475
51476         this.handleOnAvailable();
51477     },
51478
51479     /**
51480      * Applies the configuration parameters that were passed into the constructor.
51481      * This is supposed to happen at each level through the inheritance chain.  So
51482      * a DDProxy implentation will execute apply config on DDProxy, DD, and
51483      * DragDrop in order to get all of the parameters that are available in
51484      * each object.
51485      * @method applyConfig
51486      */
51487     applyConfig: function() {
51488
51489         // configurable properties:
51490         //    padding, isTarget, maintainOffset, primaryButtonOnly
51491         this.padding           = this.config.padding || [0, 0, 0, 0];
51492         this.isTarget          = (this.config.isTarget !== false);
51493         this.maintainOffset    = (this.config.maintainOffset);
51494         this.primaryButtonOnly = (this.config.primaryButtonOnly !== false);
51495
51496     },
51497
51498     /**
51499      * Executed when the linked element is available
51500      * @method handleOnAvailable
51501      * @private
51502      */
51503     handleOnAvailable: function() {
51504         this.available = true;
51505         this.resetConstraints();
51506         this.onAvailable();
51507     },
51508
51509      /**
51510      * Configures the padding for the target zone in px.  Effectively expands
51511      * (or reduces) the virtual object size for targeting calculations.
51512      * Supports css-style shorthand; if only one parameter is passed, all sides
51513      * will have that padding, and if only two are passed, the top and bottom
51514      * will have the first param, the left and right the second.
51515      * @method setPadding
51516      * @param {int} iTop    Top pad
51517      * @param {int} iRight  Right pad
51518      * @param {int} iBot    Bot pad
51519      * @param {int} iLeft   Left pad
51520      */
51521     setPadding: function(iTop, iRight, iBot, iLeft) {
51522         // this.padding = [iLeft, iRight, iTop, iBot];
51523         if (!iRight && 0 !== iRight) {
51524             this.padding = [iTop, iTop, iTop, iTop];
51525         } else if (!iBot && 0 !== iBot) {
51526             this.padding = [iTop, iRight, iTop, iRight];
51527         } else {
51528             this.padding = [iTop, iRight, iBot, iLeft];
51529         }
51530     },
51531
51532     /**
51533      * Stores the initial placement of the linked element.
51534      * @method setInitPosition
51535      * @param {int} diffX   the X offset, default 0
51536      * @param {int} diffY   the Y offset, default 0
51537      */
51538     setInitPosition: function(diffX, diffY) {
51539         var el = this.getEl();
51540
51541         if (!this.DDMInstance.verifyEl(el)) {
51542             return;
51543         }
51544
51545         var dx = diffX || 0;
51546         var dy = diffY || 0;
51547
51548         var p = Ext.core.Element.getXY( el );
51549
51550         this.initPageX = p[0] - dx;
51551         this.initPageY = p[1] - dy;
51552
51553         this.lastPageX = p[0];
51554         this.lastPageY = p[1];
51555
51556         this.setStartPosition(p);
51557     },
51558
51559     /**
51560      * Sets the start position of the element.  This is set when the obj
51561      * is initialized, the reset when a drag is started.
51562      * @method setStartPosition
51563      * @param pos current position (from previous lookup)
51564      * @private
51565      */
51566     setStartPosition: function(pos) {
51567         var p = pos || Ext.core.Element.getXY( this.getEl() );
51568         this.deltaSetXY = null;
51569
51570         this.startPageX = p[0];
51571         this.startPageY = p[1];
51572     },
51573
51574     /**
51575      * Add this instance to a group of related drag/drop objects.  All
51576      * instances belong to at least one group, and can belong to as many
51577      * groups as needed.
51578      * @method addToGroup
51579      * @param sGroup {string} the name of the group
51580      */
51581     addToGroup: function(sGroup) {
51582         this.groups[sGroup] = true;
51583         this.DDMInstance.regDragDrop(this, sGroup);
51584     },
51585
51586     /**
51587      * Remove's this instance from the supplied interaction group
51588      * @method removeFromGroup
51589      * @param {string}  sGroup  The group to drop
51590      */
51591     removeFromGroup: function(sGroup) {
51592         if (this.groups[sGroup]) {
51593             delete this.groups[sGroup];
51594         }
51595
51596         this.DDMInstance.removeDDFromGroup(this, sGroup);
51597     },
51598
51599     /**
51600      * Allows you to specify that an element other than the linked element
51601      * will be moved with the cursor during a drag
51602      * @method setDragElId
51603      * @param id {string} the id of the element that will be used to initiate the drag
51604      */
51605     setDragElId: function(id) {
51606         this.dragElId = id;
51607     },
51608
51609     /**
51610      * Allows you to specify a child of the linked element that should be
51611      * used to initiate the drag operation.  An example of this would be if
51612      * you have a content div with text and links.  Clicking anywhere in the
51613      * content area would normally start the drag operation.  Use this method
51614      * to specify that an element inside of the content div is the element
51615      * that starts the drag operation.
51616      * @method setHandleElId
51617      * @param id {string} the id of the element that will be used to
51618      * initiate the drag.
51619      */
51620     setHandleElId: function(id) {
51621         if (typeof id !== "string") {
51622             id = Ext.id(id);
51623         }
51624         this.handleElId = id;
51625         this.DDMInstance.regHandle(this.id, id);
51626     },
51627
51628     /**
51629      * Allows you to set an element outside of the linked element as a drag
51630      * handle
51631      * @method setOuterHandleElId
51632      * @param id the id of the element that will be used to initiate the drag
51633      */
51634     setOuterHandleElId: function(id) {
51635         if (typeof id !== "string") {
51636             id = Ext.id(id);
51637         }
51638         Ext.EventManager.on(id, "mousedown", this.handleMouseDown, this);
51639         this.setHandleElId(id);
51640
51641         this.hasOuterHandles = true;
51642     },
51643
51644     /**
51645      * Remove all drag and drop hooks for this element
51646      * @method unreg
51647      */
51648     unreg: function() {
51649         Ext.EventManager.un(this.id, "mousedown", this.handleMouseDown, this);
51650         this._domRef = null;
51651         this.DDMInstance._remove(this);
51652     },
51653
51654     destroy : function(){
51655         this.unreg();
51656     },
51657
51658     /**
51659      * Returns true if this instance is locked, or the drag drop mgr is locked
51660      * (meaning that all drag/drop is disabled on the page.)
51661      * @method isLocked
51662      * @return {boolean} true if this obj or all drag/drop is locked, else
51663      * false
51664      */
51665     isLocked: function() {
51666         return (this.DDMInstance.isLocked() || this.locked);
51667     },
51668
51669     /**
51670      * Fired when this object is clicked
51671      * @method handleMouseDown
51672      * @param {Event} e
51673      * @param {Ext.dd.DragDrop} oDD the clicked dd object (this dd obj)
51674      * @private
51675      */
51676     handleMouseDown: function(e, oDD){
51677         if (this.primaryButtonOnly && e.button != 0) {
51678             return;
51679         }
51680
51681         if (this.isLocked()) {
51682             return;
51683         }
51684
51685         this.DDMInstance.refreshCache(this.groups);
51686
51687         var pt = e.getPoint();
51688         if (!this.hasOuterHandles && !this.DDMInstance.isOverTarget(pt, this) )  {
51689         } else {
51690             if (this.clickValidator(e)) {
51691                 // set the initial element position
51692                 this.setStartPosition();
51693                 this.b4MouseDown(e);
51694                 this.onMouseDown(e);
51695
51696                 this.DDMInstance.handleMouseDown(e, this);
51697
51698                 this.DDMInstance.stopEvent(e);
51699             } else {
51700
51701
51702             }
51703         }
51704     },
51705
51706     clickValidator: function(e) {
51707         var target = e.getTarget();
51708         return ( this.isValidHandleChild(target) &&
51709                     (this.id == this.handleElId ||
51710                         this.DDMInstance.handleWasClicked(target, this.id)) );
51711     },
51712
51713     /**
51714      * Allows you to specify a tag name that should not start a drag operation
51715      * when clicked.  This is designed to facilitate embedding links within a
51716      * drag handle that do something other than start the drag.
51717      * @method addInvalidHandleType
51718      * @param {string} tagName the type of element to exclude
51719      */
51720     addInvalidHandleType: function(tagName) {
51721         var type = tagName.toUpperCase();
51722         this.invalidHandleTypes[type] = type;
51723     },
51724
51725     /**
51726      * Lets you to specify an element id for a child of a drag handle
51727      * that should not initiate a drag
51728      * @method addInvalidHandleId
51729      * @param {string} id the element id of the element you wish to ignore
51730      */
51731     addInvalidHandleId: function(id) {
51732         if (typeof id !== "string") {
51733             id = Ext.id(id);
51734         }
51735         this.invalidHandleIds[id] = id;
51736     },
51737
51738     /**
51739      * Lets you specify a css class of elements that will not initiate a drag
51740      * @method addInvalidHandleClass
51741      * @param {string} cssClass the class of the elements you wish to ignore
51742      */
51743     addInvalidHandleClass: function(cssClass) {
51744         this.invalidHandleClasses.push(cssClass);
51745     },
51746
51747     /**
51748      * Unsets an excluded tag name set by addInvalidHandleType
51749      * @method removeInvalidHandleType
51750      * @param {string} tagName the type of element to unexclude
51751      */
51752     removeInvalidHandleType: function(tagName) {
51753         var type = tagName.toUpperCase();
51754         // this.invalidHandleTypes[type] = null;
51755         delete this.invalidHandleTypes[type];
51756     },
51757
51758     /**
51759      * Unsets an invalid handle id
51760      * @method removeInvalidHandleId
51761      * @param {string} id the id of the element to re-enable
51762      */
51763     removeInvalidHandleId: function(id) {
51764         if (typeof id !== "string") {
51765             id = Ext.id(id);
51766         }
51767         delete this.invalidHandleIds[id];
51768     },
51769
51770     /**
51771      * Unsets an invalid css class
51772      * @method removeInvalidHandleClass
51773      * @param {string} cssClass the class of the element(s) you wish to
51774      * re-enable
51775      */
51776     removeInvalidHandleClass: function(cssClass) {
51777         for (var i=0, len=this.invalidHandleClasses.length; i<len; ++i) {
51778             if (this.invalidHandleClasses[i] == cssClass) {
51779                 delete this.invalidHandleClasses[i];
51780             }
51781         }
51782     },
51783
51784     /**
51785      * Checks the tag exclusion list to see if this click should be ignored
51786      * @method isValidHandleChild
51787      * @param {HTMLElement} node the HTMLElement to evaluate
51788      * @return {boolean} true if this is a valid tag type, false if not
51789      */
51790     isValidHandleChild: function(node) {
51791
51792         var valid = true;
51793         // var n = (node.nodeName == "#text") ? node.parentNode : node;
51794         var nodeName;
51795         try {
51796             nodeName = node.nodeName.toUpperCase();
51797         } catch(e) {
51798             nodeName = node.nodeName;
51799         }
51800         valid = valid && !this.invalidHandleTypes[nodeName];
51801         valid = valid && !this.invalidHandleIds[node.id];
51802
51803         for (var i=0, len=this.invalidHandleClasses.length; valid && i<len; ++i) {
51804             valid = !Ext.fly(node).hasCls(this.invalidHandleClasses[i]);
51805         }
51806
51807
51808         return valid;
51809
51810     },
51811
51812     /**
51813      * Create the array of horizontal tick marks if an interval was specified
51814      * in setXConstraint().
51815      * @method setXTicks
51816      * @private
51817      */
51818     setXTicks: function(iStartX, iTickSize) {
51819         this.xTicks = [];
51820         this.xTickSize = iTickSize;
51821
51822         var tickMap = {};
51823
51824         for (var i = this.initPageX; i >= this.minX; i = i - iTickSize) {
51825             if (!tickMap[i]) {
51826                 this.xTicks[this.xTicks.length] = i;
51827                 tickMap[i] = true;
51828             }
51829         }
51830
51831         for (i = this.initPageX; i <= this.maxX; i = i + iTickSize) {
51832             if (!tickMap[i]) {
51833                 this.xTicks[this.xTicks.length] = i;
51834                 tickMap[i] = true;
51835             }
51836         }
51837
51838         Ext.Array.sort(this.xTicks, this.DDMInstance.numericSort);
51839     },
51840
51841     /**
51842      * Create the array of vertical tick marks if an interval was specified in
51843      * setYConstraint().
51844      * @method setYTicks
51845      * @private
51846      */
51847     setYTicks: function(iStartY, iTickSize) {
51848         this.yTicks = [];
51849         this.yTickSize = iTickSize;
51850
51851         var tickMap = {};
51852
51853         for (var i = this.initPageY; i >= this.minY; i = i - iTickSize) {
51854             if (!tickMap[i]) {
51855                 this.yTicks[this.yTicks.length] = i;
51856                 tickMap[i] = true;
51857             }
51858         }
51859
51860         for (i = this.initPageY; i <= this.maxY; i = i + iTickSize) {
51861             if (!tickMap[i]) {
51862                 this.yTicks[this.yTicks.length] = i;
51863                 tickMap[i] = true;
51864             }
51865         }
51866
51867         Ext.Array.sort(this.yTicks, this.DDMInstance.numericSort);
51868     },
51869
51870     /**
51871      * By default, the element can be dragged any place on the screen.  Use
51872      * this method to limit the horizontal travel of the element.  Pass in
51873      * 0,0 for the parameters if you want to lock the drag to the y axis.
51874      * @method setXConstraint
51875      * @param {int} iLeft the number of pixels the element can move to the left
51876      * @param {int} iRight the number of pixels the element can move to the
51877      * right
51878      * @param {int} iTickSize optional parameter for specifying that the
51879      * element
51880      * should move iTickSize pixels at a time.
51881      */
51882     setXConstraint: function(iLeft, iRight, iTickSize) {
51883         this.leftConstraint = iLeft;
51884         this.rightConstraint = iRight;
51885
51886         this.minX = this.initPageX - iLeft;
51887         this.maxX = this.initPageX + iRight;
51888         if (iTickSize) { this.setXTicks(this.initPageX, iTickSize); }
51889
51890         this.constrainX = true;
51891     },
51892
51893     /**
51894      * Clears any constraints applied to this instance.  Also clears ticks
51895      * since they can't exist independent of a constraint at this time.
51896      * @method clearConstraints
51897      */
51898     clearConstraints: function() {
51899         this.constrainX = false;
51900         this.constrainY = false;
51901         this.clearTicks();
51902     },
51903
51904     /**
51905      * Clears any tick interval defined for this instance
51906      * @method clearTicks
51907      */
51908     clearTicks: function() {
51909         this.xTicks = null;
51910         this.yTicks = null;
51911         this.xTickSize = 0;
51912         this.yTickSize = 0;
51913     },
51914
51915     /**
51916      * By default, the element can be dragged any place on the screen.  Set
51917      * this to limit the vertical travel of the element.  Pass in 0,0 for the
51918      * parameters if you want to lock the drag to the x axis.
51919      * @method setYConstraint
51920      * @param {int} iUp the number of pixels the element can move up
51921      * @param {int} iDown the number of pixels the element can move down
51922      * @param {int} iTickSize optional parameter for specifying that the
51923      * element should move iTickSize pixels at a time.
51924      */
51925     setYConstraint: function(iUp, iDown, iTickSize) {
51926         this.topConstraint = iUp;
51927         this.bottomConstraint = iDown;
51928
51929         this.minY = this.initPageY - iUp;
51930         this.maxY = this.initPageY + iDown;
51931         if (iTickSize) { this.setYTicks(this.initPageY, iTickSize); }
51932
51933         this.constrainY = true;
51934
51935     },
51936
51937     /**
51938      * resetConstraints must be called if you manually reposition a dd element.
51939      * @method resetConstraints
51940      * @param {boolean} maintainOffset
51941      */
51942     resetConstraints: function() {
51943         // Maintain offsets if necessary
51944         if (this.initPageX || this.initPageX === 0) {
51945             // figure out how much this thing has moved
51946             var dx = (this.maintainOffset) ? this.lastPageX - this.initPageX : 0;
51947             var dy = (this.maintainOffset) ? this.lastPageY - this.initPageY : 0;
51948
51949             this.setInitPosition(dx, dy);
51950
51951         // This is the first time we have detected the element's position
51952         } else {
51953             this.setInitPosition();
51954         }
51955
51956         if (this.constrainX) {
51957             this.setXConstraint( this.leftConstraint,
51958                                  this.rightConstraint,
51959                                  this.xTickSize        );
51960         }
51961
51962         if (this.constrainY) {
51963             this.setYConstraint( this.topConstraint,
51964                                  this.bottomConstraint,
51965                                  this.yTickSize         );
51966         }
51967     },
51968
51969     /**
51970      * Normally the drag element is moved pixel by pixel, but we can specify
51971      * that it move a number of pixels at a time.  This method resolves the
51972      * location when we have it set up like this.
51973      * @method getTick
51974      * @param {int} val where we want to place the object
51975      * @param {int[]} tickArray sorted array of valid points
51976      * @return {int} the closest tick
51977      * @private
51978      */
51979     getTick: function(val, tickArray) {
51980         if (!tickArray) {
51981             // If tick interval is not defined, it is effectively 1 pixel,
51982             // so we return the value passed to us.
51983             return val;
51984         } else if (tickArray[0] >= val) {
51985             // The value is lower than the first tick, so we return the first
51986             // tick.
51987             return tickArray[0];
51988         } else {
51989             for (var i=0, len=tickArray.length; i<len; ++i) {
51990                 var next = i + 1;
51991                 if (tickArray[next] && tickArray[next] >= val) {
51992                     var diff1 = val - tickArray[i];
51993                     var diff2 = tickArray[next] - val;
51994                     return (diff2 > diff1) ? tickArray[i] : tickArray[next];
51995                 }
51996             }
51997
51998             // The value is larger than the last tick, so we return the last
51999             // tick.
52000             return tickArray[tickArray.length - 1];
52001         }
52002     },
52003
52004     /**
52005      * toString method
52006      * @method toString
52007      * @return {string} string representation of the dd obj
52008      */
52009     toString: function() {
52010         return ("DragDrop " + this.id);
52011     }
52012
52013 });
52014 /*
52015  * This is a derivative of the similarly named class in the YUI Library.
52016  * The original license:
52017  * Copyright (c) 2006, Yahoo! Inc. All rights reserved.
52018  * Code licensed under the BSD License:
52019  * http://developer.yahoo.net/yui/license.txt
52020  */
52021
52022
52023 /**
52024  * @class Ext.dd.DD
52025  * A DragDrop implementation where the linked element follows the
52026  * mouse cursor during a drag.
52027  * @extends Ext.dd.DragDrop
52028  * @constructor
52029  * @param {String} id the id of the linked element
52030  * @param {String} sGroup the group of related DragDrop items
52031  * @param {object} config an object containing configurable attributes
52032  *                Valid properties for DD:
52033  *                    scroll
52034  */
52035
52036 Ext.define('Ext.dd.DD', {
52037     extend: 'Ext.dd.DragDrop',
52038     requires: ['Ext.dd.DragDropManager'],
52039     constructor: function(id, sGroup, config) {
52040         if (id) {
52041             this.init(id, sGroup, config);
52042         }
52043     },
52044
52045     /**
52046      * When set to true, the utility automatically tries to scroll the browser
52047      * window when a drag and drop element is dragged near the viewport boundary.
52048      * Defaults to true.
52049      * @property scroll
52050      * @type boolean
52051      */
52052     scroll: true,
52053
52054     /**
52055      * Sets the pointer offset to the distance between the linked element's top
52056      * left corner and the location the element was clicked
52057      * @method autoOffset
52058      * @param {int} iPageX the X coordinate of the click
52059      * @param {int} iPageY the Y coordinate of the click
52060      */
52061     autoOffset: function(iPageX, iPageY) {
52062         var x = iPageX - this.startPageX;
52063         var y = iPageY - this.startPageY;
52064         this.setDelta(x, y);
52065     },
52066
52067     /**
52068      * Sets the pointer offset.  You can call this directly to force the
52069      * offset to be in a particular location (e.g., pass in 0,0 to set it
52070      * to the center of the object)
52071      * @method setDelta
52072      * @param {int} iDeltaX the distance from the left
52073      * @param {int} iDeltaY the distance from the top
52074      */
52075     setDelta: function(iDeltaX, iDeltaY) {
52076         this.deltaX = iDeltaX;
52077         this.deltaY = iDeltaY;
52078     },
52079
52080     /**
52081      * Sets the drag element to the location of the mousedown or click event,
52082      * maintaining the cursor location relative to the location on the element
52083      * that was clicked.  Override this if you want to place the element in a
52084      * location other than where the cursor is.
52085      * @method setDragElPos
52086      * @param {int} iPageX the X coordinate of the mousedown or drag event
52087      * @param {int} iPageY the Y coordinate of the mousedown or drag event
52088      */
52089     setDragElPos: function(iPageX, iPageY) {
52090         // the first time we do this, we are going to check to make sure
52091         // the element has css positioning
52092
52093         var el = this.getDragEl();
52094         this.alignElWithMouse(el, iPageX, iPageY);
52095     },
52096
52097     /**
52098      * Sets the element to the location of the mousedown or click event,
52099      * maintaining the cursor location relative to the location on the element
52100      * that was clicked.  Override this if you want to place the element in a
52101      * location other than where the cursor is.
52102      * @method alignElWithMouse
52103      * @param {HTMLElement} el the element to move
52104      * @param {int} iPageX the X coordinate of the mousedown or drag event
52105      * @param {int} iPageY the Y coordinate of the mousedown or drag event
52106      */
52107     alignElWithMouse: function(el, iPageX, iPageY) {
52108         var oCoord = this.getTargetCoord(iPageX, iPageY),
52109             fly = el.dom ? el : Ext.fly(el, '_dd'),
52110             elSize = fly.getSize(),
52111             EL = Ext.core.Element,
52112             vpSize;
52113
52114         if (!this.deltaSetXY) {
52115             vpSize = this.cachedViewportSize = { width: EL.getDocumentWidth(), height: EL.getDocumentHeight() };
52116             var aCoord = [
52117                 Math.max(0, Math.min(oCoord.x, vpSize.width - elSize.width)),
52118                 Math.max(0, Math.min(oCoord.y, vpSize.height - elSize.height))
52119             ];
52120             fly.setXY(aCoord);
52121             var newLeft = fly.getLeft(true);
52122             var newTop  = fly.getTop(true);
52123             this.deltaSetXY = [newLeft - oCoord.x, newTop - oCoord.y];
52124         } else {
52125             vpSize = this.cachedViewportSize;
52126             fly.setLeftTop(
52127                 Math.max(0, Math.min(oCoord.x + this.deltaSetXY[0], vpSize.width - elSize.width)),
52128                 Math.max(0, Math.min(oCoord.y + this.deltaSetXY[1], vpSize.height - elSize.height))
52129             );
52130         }
52131
52132         this.cachePosition(oCoord.x, oCoord.y);
52133         this.autoScroll(oCoord.x, oCoord.y, el.offsetHeight, el.offsetWidth);
52134         return oCoord;
52135     },
52136
52137     /**
52138      * Saves the most recent position so that we can reset the constraints and
52139      * tick marks on-demand.  We need to know this so that we can calculate the
52140      * number of pixels the element is offset from its original position.
52141      * @method cachePosition
52142      * @param iPageX the current x position (optional, this just makes it so we
52143      * don't have to look it up again)
52144      * @param iPageY the current y position (optional, this just makes it so we
52145      * don't have to look it up again)
52146      */
52147     cachePosition: function(iPageX, iPageY) {
52148         if (iPageX) {
52149             this.lastPageX = iPageX;
52150             this.lastPageY = iPageY;
52151         } else {
52152             var aCoord = Ext.core.Element.getXY(this.getEl());
52153             this.lastPageX = aCoord[0];
52154             this.lastPageY = aCoord[1];
52155         }
52156     },
52157
52158     /**
52159      * Auto-scroll the window if the dragged object has been moved beyond the
52160      * visible window boundary.
52161      * @method autoScroll
52162      * @param {int} x the drag element's x position
52163      * @param {int} y the drag element's y position
52164      * @param {int} h the height of the drag element
52165      * @param {int} w the width of the drag element
52166      * @private
52167      */
52168     autoScroll: function(x, y, h, w) {
52169
52170         if (this.scroll) {
52171             // The client height
52172             var clientH = Ext.core.Element.getViewHeight();
52173
52174             // The client width
52175             var clientW = Ext.core.Element.getViewWidth();
52176
52177             // The amt scrolled down
52178             var st = this.DDMInstance.getScrollTop();
52179
52180             // The amt scrolled right
52181             var sl = this.DDMInstance.getScrollLeft();
52182
52183             // Location of the bottom of the element
52184             var bot = h + y;
52185
52186             // Location of the right of the element
52187             var right = w + x;
52188
52189             // The distance from the cursor to the bottom of the visible area,
52190             // adjusted so that we don't scroll if the cursor is beyond the
52191             // element drag constraints
52192             var toBot = (clientH + st - y - this.deltaY);
52193
52194             // The distance from the cursor to the right of the visible area
52195             var toRight = (clientW + sl - x - this.deltaX);
52196
52197
52198             // How close to the edge the cursor must be before we scroll
52199             // var thresh = (document.all) ? 100 : 40;
52200             var thresh = 40;
52201
52202             // How many pixels to scroll per autoscroll op.  This helps to reduce
52203             // clunky scrolling. IE is more sensitive about this ... it needs this
52204             // value to be higher.
52205             var scrAmt = (document.all) ? 80 : 30;
52206
52207             // Scroll down if we are near the bottom of the visible page and the
52208             // obj extends below the crease
52209             if ( bot > clientH && toBot < thresh ) {
52210                 window.scrollTo(sl, st + scrAmt);
52211             }
52212
52213             // Scroll up if the window is scrolled down and the top of the object
52214             // goes above the top border
52215             if ( y < st && st > 0 && y - st < thresh ) {
52216                 window.scrollTo(sl, st - scrAmt);
52217             }
52218
52219             // Scroll right if the obj is beyond the right border and the cursor is
52220             // near the border.
52221             if ( right > clientW && toRight < thresh ) {
52222                 window.scrollTo(sl + scrAmt, st);
52223             }
52224
52225             // Scroll left if the window has been scrolled to the right and the obj
52226             // extends past the left border
52227             if ( x < sl && sl > 0 && x - sl < thresh ) {
52228                 window.scrollTo(sl - scrAmt, st);
52229             }
52230         }
52231     },
52232
52233     /**
52234      * Finds the location the element should be placed if we want to move
52235      * it to where the mouse location less the click offset would place us.
52236      * @method getTargetCoord
52237      * @param {int} iPageX the X coordinate of the click
52238      * @param {int} iPageY the Y coordinate of the click
52239      * @return an object that contains the coordinates (Object.x and Object.y)
52240      * @private
52241      */
52242     getTargetCoord: function(iPageX, iPageY) {
52243         var x = iPageX - this.deltaX;
52244         var y = iPageY - this.deltaY;
52245
52246         if (this.constrainX) {
52247             if (x < this.minX) {
52248                 x = this.minX;
52249             }
52250             if (x > this.maxX) {
52251                 x = this.maxX;
52252             }
52253         }
52254
52255         if (this.constrainY) {
52256             if (y < this.minY) {
52257                 y = this.minY;
52258             }
52259             if (y > this.maxY) {
52260                 y = this.maxY;
52261             }
52262         }
52263
52264         x = this.getTick(x, this.xTicks);
52265         y = this.getTick(y, this.yTicks);
52266
52267
52268         return {x: x, y: y};
52269     },
52270
52271     /**
52272      * Sets up config options specific to this class. Overrides
52273      * Ext.dd.DragDrop, but all versions of this method through the
52274      * inheritance chain are called
52275      */
52276     applyConfig: function() {
52277         this.callParent();
52278         this.scroll = (this.config.scroll !== false);
52279     },
52280
52281     /**
52282      * Event that fires prior to the onMouseDown event.  Overrides
52283      * Ext.dd.DragDrop.
52284      */
52285     b4MouseDown: function(e) {
52286         // this.resetConstraints();
52287         this.autoOffset(e.getPageX(), e.getPageY());
52288     },
52289
52290     /**
52291      * Event that fires prior to the onDrag event.  Overrides
52292      * Ext.dd.DragDrop.
52293      */
52294     b4Drag: function(e) {
52295         this.setDragElPos(e.getPageX(), e.getPageY());
52296     },
52297
52298     toString: function() {
52299         return ("DD " + this.id);
52300     }
52301
52302     //////////////////////////////////////////////////////////////////////////
52303     // Debugging ygDragDrop events that can be overridden
52304     //////////////////////////////////////////////////////////////////////////
52305     /*
52306     startDrag: function(x, y) {
52307     },
52308
52309     onDrag: function(e) {
52310     },
52311
52312     onDragEnter: function(e, id) {
52313     },
52314
52315     onDragOver: function(e, id) {
52316     },
52317
52318     onDragOut: function(e, id) {
52319     },
52320
52321     onDragDrop: function(e, id) {
52322     },
52323
52324     endDrag: function(e) {
52325     }
52326
52327     */
52328
52329 });
52330
52331 /*
52332  * This is a derivative of the similarly named class in the YUI Library.
52333  * The original license:
52334  * Copyright (c) 2006, Yahoo! Inc. All rights reserved.
52335  * Code licensed under the BSD License:
52336  * http://developer.yahoo.net/yui/license.txt
52337  */
52338
52339 /**
52340  * @class Ext.dd.DDProxy
52341  * A DragDrop implementation that inserts an empty, bordered div into
52342  * the document that follows the cursor during drag operations.  At the time of
52343  * the click, the frame div is resized to the dimensions of the linked html
52344  * element, and moved to the exact location of the linked element.
52345  *
52346  * References to the "frame" element refer to the single proxy element that
52347  * was created to be dragged in place of all DDProxy elements on the
52348  * page.
52349  *
52350  * @extends Ext.dd.DD
52351  * @constructor
52352  * @param {String} id the id of the linked html element
52353  * @param {String} sGroup the group of related DragDrop objects
52354  * @param {object} config an object containing configurable attributes
52355  *                Valid properties for DDProxy in addition to those in DragDrop:
52356  *                   resizeFrame, centerFrame, dragElId
52357  */
52358 Ext.define('Ext.dd.DDProxy', {
52359     extend: 'Ext.dd.DD',
52360
52361     statics: {
52362         /**
52363          * The default drag frame div id
52364          * @property Ext.dd.DDProxy.dragElId
52365          * @type String
52366          * @static
52367          */
52368         dragElId: "ygddfdiv"
52369     },
52370
52371     constructor: function(id, sGroup, config) {
52372         if (id) {
52373             this.init(id, sGroup, config);
52374             this.initFrame();
52375         }
52376     },
52377
52378     /**
52379      * By default we resize the drag frame to be the same size as the element
52380      * we want to drag (this is to get the frame effect).  We can turn it off
52381      * if we want a different behavior.
52382      * @property resizeFrame
52383      * @type boolean
52384      */
52385     resizeFrame: true,
52386
52387     /**
52388      * By default the frame is positioned exactly where the drag element is, so
52389      * we use the cursor offset provided by Ext.dd.DD.  Another option that works only if
52390      * you do not have constraints on the obj is to have the drag frame centered
52391      * around the cursor.  Set centerFrame to true for this effect.
52392      * @property centerFrame
52393      * @type boolean
52394      */
52395     centerFrame: false,
52396
52397     /**
52398      * Creates the proxy element if it does not yet exist
52399      * @method createFrame
52400      */
52401     createFrame: function() {
52402         var self = this;
52403         var body = document.body;
52404
52405         if (!body || !body.firstChild) {
52406             setTimeout( function() { self.createFrame(); }, 50 );
52407             return;
52408         }
52409
52410         var div = this.getDragEl();
52411
52412         if (!div) {
52413             div    = document.createElement("div");
52414             div.id = this.dragElId;
52415             var s  = div.style;
52416
52417             s.position   = "absolute";
52418             s.visibility = "hidden";
52419             s.cursor     = "move";
52420             s.border     = "2px solid #aaa";
52421             s.zIndex     = 999;
52422
52423             // appendChild can blow up IE if invoked prior to the window load event
52424             // while rendering a table.  It is possible there are other scenarios
52425             // that would cause this to happen as well.
52426             body.insertBefore(div, body.firstChild);
52427         }
52428     },
52429
52430     /**
52431      * Initialization for the drag frame element.  Must be called in the
52432      * constructor of all subclasses
52433      * @method initFrame
52434      */
52435     initFrame: function() {
52436         this.createFrame();
52437     },
52438
52439     applyConfig: function() {
52440         this.callParent();
52441
52442         this.resizeFrame = (this.config.resizeFrame !== false);
52443         this.centerFrame = (this.config.centerFrame);
52444         this.setDragElId(this.config.dragElId || Ext.dd.DDProxy.dragElId);
52445     },
52446
52447     /**
52448      * Resizes the drag frame to the dimensions of the clicked object, positions
52449      * it over the object, and finally displays it
52450      * @method showFrame
52451      * @param {int} iPageX X click position
52452      * @param {int} iPageY Y click position
52453      * @private
52454      */
52455     showFrame: function(iPageX, iPageY) {
52456         var el = this.getEl();
52457         var dragEl = this.getDragEl();
52458         var s = dragEl.style;
52459
52460         this._resizeProxy();
52461
52462         if (this.centerFrame) {
52463             this.setDelta( Math.round(parseInt(s.width,  10)/2),
52464                            Math.round(parseInt(s.height, 10)/2) );
52465         }
52466
52467         this.setDragElPos(iPageX, iPageY);
52468
52469         Ext.fly(dragEl).show();
52470     },
52471
52472     /**
52473      * The proxy is automatically resized to the dimensions of the linked
52474      * element when a drag is initiated, unless resizeFrame is set to false
52475      * @method _resizeProxy
52476      * @private
52477      */
52478     _resizeProxy: function() {
52479         if (this.resizeFrame) {
52480             var el = this.getEl();
52481             Ext.fly(this.getDragEl()).setSize(el.offsetWidth, el.offsetHeight);
52482         }
52483     },
52484
52485     // overrides Ext.dd.DragDrop
52486     b4MouseDown: function(e) {
52487         var x = e.getPageX();
52488         var y = e.getPageY();
52489         this.autoOffset(x, y);
52490         this.setDragElPos(x, y);
52491     },
52492
52493     // overrides Ext.dd.DragDrop
52494     b4StartDrag: function(x, y) {
52495         // show the drag frame
52496         this.showFrame(x, y);
52497     },
52498
52499     // overrides Ext.dd.DragDrop
52500     b4EndDrag: function(e) {
52501         Ext.fly(this.getDragEl()).hide();
52502     },
52503
52504     // overrides Ext.dd.DragDrop
52505     // By default we try to move the element to the last location of the frame.
52506     // This is so that the default behavior mirrors that of Ext.dd.DD.
52507     endDrag: function(e) {
52508
52509         var lel = this.getEl();
52510         var del = this.getDragEl();
52511
52512         // Show the drag frame briefly so we can get its position
52513         del.style.visibility = "";
52514
52515         this.beforeMove();
52516         // Hide the linked element before the move to get around a Safari
52517         // rendering bug.
52518         lel.style.visibility = "hidden";
52519         Ext.dd.DDM.moveToEl(lel, del);
52520         del.style.visibility = "hidden";
52521         lel.style.visibility = "";
52522
52523         this.afterDrag();
52524     },
52525
52526     beforeMove : function(){
52527
52528     },
52529
52530     afterDrag : function(){
52531
52532     },
52533
52534     toString: function() {
52535         return ("DDProxy " + this.id);
52536     }
52537
52538 });
52539
52540 /**
52541  * @class Ext.dd.DragSource
52542  * @extends Ext.dd.DDProxy
52543  * A simple class that provides the basic implementation needed to make any element draggable.
52544  * @constructor
52545  * @param {Mixed} el The container element
52546  * @param {Object} config
52547  */
52548 Ext.define('Ext.dd.DragSource', {
52549     extend: 'Ext.dd.DDProxy',
52550     requires: [
52551         'Ext.dd.StatusProxy',
52552         'Ext.dd.DragDropManager'
52553     ],
52554
52555     /**
52556      * @cfg {String} ddGroup
52557      * A named drag drop group to which this object belongs.  If a group is specified, then this object will only
52558      * interact with other drag drop objects in the same group (defaults to undefined).
52559      */
52560
52561     /**
52562      * @cfg {String} dropAllowed
52563      * The CSS class returned to the drag source when drop is allowed (defaults to "x-dd-drop-ok").
52564      */
52565
52566     dropAllowed : Ext.baseCSSPrefix + 'dd-drop-ok',
52567     /**
52568      * @cfg {String} dropNotAllowed
52569      * The CSS class returned to the drag source when drop is not allowed (defaults to "x-dd-drop-nodrop").
52570      */
52571     dropNotAllowed : Ext.baseCSSPrefix + 'dd-drop-nodrop',
52572
52573     /**
52574      * @cfg {Boolean} animRepair
52575      * Defaults to true. If true, animates the proxy element back to the position of the handle element used to trigger the drag.
52576      */
52577     animRepair: true,
52578
52579     /**
52580      * @cfg {String} repairHighlightColor The color to use when visually highlighting the drag source in the afterRepair
52581      * method after a failed drop (defaults to 'c3daf9' - light blue). The color must be a 6 digit hex value, without
52582      * a preceding '#'.
52583      */
52584     repairHighlightColor: 'c3daf9',
52585
52586     constructor: function(el, config) {
52587         this.el = Ext.get(el);
52588         if(!this.dragData){
52589             this.dragData = {};
52590         }
52591
52592         Ext.apply(this, config);
52593
52594         if(!this.proxy){
52595             this.proxy = Ext.create('Ext.dd.StatusProxy', {
52596                 animRepair: this.animRepair
52597             });
52598         }
52599         this.callParent([this.el.dom, this.ddGroup || this.group,
52600               {dragElId : this.proxy.id, resizeFrame: false, isTarget: false, scroll: this.scroll === true}]);
52601
52602         this.dragging = false;
52603     },
52604
52605     /**
52606      * Returns the data object associated with this drag source
52607      * @return {Object} data An object containing arbitrary data
52608      */
52609     getDragData : function(e){
52610         return this.dragData;
52611     },
52612
52613     // private
52614     onDragEnter : function(e, id){
52615         var target = Ext.dd.DragDropManager.getDDById(id);
52616         this.cachedTarget = target;
52617         if (this.beforeDragEnter(target, e, id) !== false) {
52618             if (target.isNotifyTarget) {
52619                 var status = target.notifyEnter(this, e, this.dragData);
52620                 this.proxy.setStatus(status);
52621             } else {
52622                 this.proxy.setStatus(this.dropAllowed);
52623             }
52624
52625             if (this.afterDragEnter) {
52626                 /**
52627                  * An empty function by default, but provided so that you can perform a custom action
52628                  * when the dragged item enters the drop target by providing an implementation.
52629                  * @param {Ext.dd.DragDrop} target The drop target
52630                  * @param {Event} e The event object
52631                  * @param {String} id The id of the dragged element
52632                  * @method afterDragEnter
52633                  */
52634                 this.afterDragEnter(target, e, id);
52635             }
52636         }
52637     },
52638
52639     /**
52640      * An empty function by default, but provided so that you can perform a custom action
52641      * before the dragged item enters the drop target and optionally cancel the onDragEnter.
52642      * @param {Ext.dd.DragDrop} target The drop target
52643      * @param {Event} e The event object
52644      * @param {String} id The id of the dragged element
52645      * @return {Boolean} isValid True if the drag event is valid, else false to cancel
52646      */
52647     beforeDragEnter: function(target, e, id) {
52648         return true;
52649     },
52650
52651     // private
52652     alignElWithMouse: function() {
52653         this.callParent(arguments);
52654         this.proxy.sync();
52655     },
52656
52657     // private
52658     onDragOver: function(e, id) {
52659         var target = this.cachedTarget || Ext.dd.DragDropManager.getDDById(id);
52660         if (this.beforeDragOver(target, e, id) !== false) {
52661             if(target.isNotifyTarget){
52662                 var status = target.notifyOver(this, e, this.dragData);
52663                 this.proxy.setStatus(status);
52664             }
52665
52666             if (this.afterDragOver) {
52667                 /**
52668                  * An empty function by default, but provided so that you can perform a custom action
52669                  * while the dragged item is over the drop target by providing an implementation.
52670                  * @param {Ext.dd.DragDrop} target The drop target
52671                  * @param {Event} e The event object
52672                  * @param {String} id The id of the dragged element
52673                  * @method afterDragOver
52674                  */
52675                 this.afterDragOver(target, e, id);
52676             }
52677         }
52678     },
52679
52680     /**
52681      * An empty function by default, but provided so that you can perform a custom action
52682      * while the dragged item is over the drop target and optionally cancel the onDragOver.
52683      * @param {Ext.dd.DragDrop} target The drop target
52684      * @param {Event} e The event object
52685      * @param {String} id The id of the dragged element
52686      * @return {Boolean} isValid True if the drag event is valid, else false to cancel
52687      */
52688     beforeDragOver: function(target, e, id) {
52689         return true;
52690     },
52691
52692     // private
52693     onDragOut: function(e, id) {
52694         var target = this.cachedTarget || Ext.dd.DragDropManager.getDDById(id);
52695         if (this.beforeDragOut(target, e, id) !== false) {
52696             if (target.isNotifyTarget) {
52697                 target.notifyOut(this, e, this.dragData);
52698             }
52699             this.proxy.reset();
52700             if (this.afterDragOut) {
52701                 /**
52702                  * An empty function by default, but provided so that you can perform a custom action
52703                  * after the dragged item is dragged out of the target without dropping.
52704                  * @param {Ext.dd.DragDrop} target The drop target
52705                  * @param {Event} e The event object
52706                  * @param {String} id The id of the dragged element
52707                  * @method afterDragOut
52708                  */
52709                 this.afterDragOut(target, e, id);
52710             }
52711         }
52712         this.cachedTarget = null;
52713     },
52714
52715     /**
52716      * An empty function by default, but provided so that you can perform a custom action before the dragged
52717      * item is dragged out of the target without dropping, and optionally cancel the onDragOut.
52718      * @param {Ext.dd.DragDrop} target The drop target
52719      * @param {Event} e The event object
52720      * @param {String} id The id of the dragged element
52721      * @return {Boolean} isValid True if the drag event is valid, else false to cancel
52722      */
52723     beforeDragOut: function(target, e, id){
52724         return true;
52725     },
52726
52727     // private
52728     onDragDrop: function(e, id){
52729         var target = this.cachedTarget || Ext.dd.DragDropManager.getDDById(id);
52730         if (this.beforeDragDrop(target, e, id) !== false) {
52731             if (target.isNotifyTarget) {
52732                 if (target.notifyDrop(this, e, this.dragData) !== false) { // valid drop?
52733                     this.onValidDrop(target, e, id);
52734                 } else {
52735                     this.onInvalidDrop(target, e, id);
52736                 }
52737             } else {
52738                 this.onValidDrop(target, e, id);
52739             }
52740
52741             if (this.afterDragDrop) {
52742                 /**
52743                  * An empty function by default, but provided so that you can perform a custom action
52744                  * after a valid drag drop has occurred by providing an implementation.
52745                  * @param {Ext.dd.DragDrop} target The drop target
52746                  * @param {Event} e The event object
52747                  * @param {String} id The id of the dropped element
52748                  * @method afterDragDrop
52749                  */
52750                 this.afterDragDrop(target, e, id);
52751             }
52752         }
52753         delete this.cachedTarget;
52754     },
52755
52756     /**
52757      * An empty function by default, but provided so that you can perform a custom action before the dragged
52758      * item is dropped onto the target and optionally cancel the onDragDrop.
52759      * @param {Ext.dd.DragDrop} target The drop target
52760      * @param {Event} e The event object
52761      * @param {String} id The id of the dragged element
52762      * @return {Boolean} isValid True if the drag drop event is valid, else false to cancel
52763      */
52764     beforeDragDrop: function(target, e, id){
52765         return true;
52766     },
52767
52768     // private
52769     onValidDrop: function(target, e, id){
52770         this.hideProxy();
52771         if(this.afterValidDrop){
52772             /**
52773              * An empty function by default, but provided so that you can perform a custom action
52774              * after a valid drop has occurred by providing an implementation.
52775              * @param {Object} target The target DD
52776              * @param {Event} e The event object
52777              * @param {String} id The id of the dropped element
52778              * @method afterInvalidDrop
52779              */
52780             this.afterValidDrop(target, e, id);
52781         }
52782     },
52783
52784     // private
52785     getRepairXY: function(e, data){
52786         return this.el.getXY();
52787     },
52788
52789     // private
52790     onInvalidDrop: function(target, e, id) {
52791         this.beforeInvalidDrop(target, e, id);
52792         if (this.cachedTarget) {
52793             if(this.cachedTarget.isNotifyTarget){
52794                 this.cachedTarget.notifyOut(this, e, this.dragData);
52795             }
52796             this.cacheTarget = null;
52797         }
52798         this.proxy.repair(this.getRepairXY(e, this.dragData), this.afterRepair, this);
52799
52800         if (this.afterInvalidDrop) {
52801             /**
52802              * An empty function by default, but provided so that you can perform a custom action
52803              * after an invalid drop has occurred by providing an implementation.
52804              * @param {Event} e The event object
52805              * @param {String} id The id of the dropped element
52806              * @method afterInvalidDrop
52807              */
52808             this.afterInvalidDrop(e, id);
52809         }
52810     },
52811
52812     // private
52813     afterRepair: function() {
52814         var me = this;
52815         if (Ext.enableFx) {
52816             me.el.highlight(me.repairHighlightColor);
52817         }
52818         me.dragging = false;
52819     },
52820
52821     /**
52822      * An empty function by default, but provided so that you can perform a custom action after an invalid
52823      * drop has occurred.
52824      * @param {Ext.dd.DragDrop} target The drop target
52825      * @param {Event} e The event object
52826      * @param {String} id The id of the dragged element
52827      * @return {Boolean} isValid True if the invalid drop should proceed, else false to cancel
52828      */
52829     beforeInvalidDrop: function(target, e, id) {
52830         return true;
52831     },
52832
52833     // private
52834     handleMouseDown: function(e) {
52835         if (this.dragging) {
52836             return;
52837         }
52838         var data = this.getDragData(e);
52839         if (data && this.onBeforeDrag(data, e) !== false) {
52840             this.dragData = data;
52841             this.proxy.stop();
52842             this.callParent(arguments);
52843         }
52844     },
52845
52846     /**
52847      * An empty function by default, but provided so that you can perform a custom action before the initial
52848      * drag event begins and optionally cancel it.
52849      * @param {Object} data An object containing arbitrary data to be shared with drop targets
52850      * @param {Event} e The event object
52851      * @return {Boolean} isValid True if the drag event is valid, else false to cancel
52852      */
52853     onBeforeDrag: function(data, e){
52854         return true;
52855     },
52856
52857     /**
52858      * An empty function by default, but provided so that you can perform a custom action once the initial
52859      * drag event has begun.  The drag cannot be canceled from this function.
52860      * @param {Number} x The x position of the click on the dragged object
52861      * @param {Number} y The y position of the click on the dragged object
52862      */
52863     onStartDrag: Ext.emptyFn,
52864
52865     // private override
52866     startDrag: function(x, y) {
52867         this.proxy.reset();
52868         this.dragging = true;
52869         this.proxy.update("");
52870         this.onInitDrag(x, y);
52871         this.proxy.show();
52872     },
52873
52874     // private
52875     onInitDrag: function(x, y) {
52876         var clone = this.el.dom.cloneNode(true);
52877         clone.id = Ext.id(); // prevent duplicate ids
52878         this.proxy.update(clone);
52879         this.onStartDrag(x, y);
52880         return true;
52881     },
52882
52883     /**
52884      * Returns the drag source's underlying {@link Ext.dd.StatusProxy}
52885      * @return {Ext.dd.StatusProxy} proxy The StatusProxy
52886      */
52887     getProxy: function() {
52888         return this.proxy;
52889     },
52890
52891     /**
52892      * Hides the drag source's {@link Ext.dd.StatusProxy}
52893      */
52894     hideProxy: function() {
52895         this.proxy.hide();
52896         this.proxy.reset(true);
52897         this.dragging = false;
52898     },
52899
52900     // private
52901     triggerCacheRefresh: function() {
52902         Ext.dd.DDM.refreshCache(this.groups);
52903     },
52904
52905     // private - override to prevent hiding
52906     b4EndDrag: function(e) {
52907     },
52908
52909     // private - override to prevent moving
52910     endDrag : function(e){
52911         this.onEndDrag(this.dragData, e);
52912     },
52913
52914     // private
52915     onEndDrag : function(data, e){
52916     },
52917
52918     // private - pin to cursor
52919     autoOffset : function(x, y) {
52920         this.setDelta(-12, -20);
52921     },
52922
52923     destroy: function(){
52924         this.callParent();
52925         Ext.destroy(this.proxy);
52926     }
52927 });
52928
52929 // private - DD implementation for Panels
52930 Ext.define('Ext.panel.DD', {
52931     extend: 'Ext.dd.DragSource',
52932     requires: ['Ext.panel.Proxy'],
52933
52934     constructor : function(panel, cfg){
52935         this.panel = panel;
52936         this.dragData = {panel: panel};
52937         this.proxy = Ext.create('Ext.panel.Proxy', panel, cfg);
52938
52939         this.callParent([panel.el, cfg]);
52940
52941         Ext.defer(function() {
52942             var header = panel.header,
52943                 el = panel.body;
52944
52945             if(header){
52946                 this.setHandleElId(header.id);
52947                 el = header.el;
52948             }
52949             el.setStyle('cursor', 'move');
52950             this.scroll = false;
52951         }, 200, this);
52952     },
52953
52954     showFrame: Ext.emptyFn,
52955     startDrag: Ext.emptyFn,
52956     b4StartDrag: function(x, y) {
52957         this.proxy.show();
52958     },
52959     b4MouseDown: function(e) {
52960         var x = e.getPageX(),
52961             y = e.getPageY();
52962         this.autoOffset(x, y);
52963     },
52964     onInitDrag : function(x, y){
52965         this.onStartDrag(x, y);
52966         return true;
52967     },
52968     createFrame : Ext.emptyFn,
52969     getDragEl : function(e){
52970         return this.proxy.ghost.el.dom;
52971     },
52972     endDrag : function(e){
52973         this.proxy.hide();
52974         this.panel.saveState();
52975     },
52976
52977     autoOffset : function(x, y) {
52978         x -= this.startPageX;
52979         y -= this.startPageY;
52980         this.setDelta(x, y);
52981     }
52982 });
52983
52984 /**
52985  * @class Ext.layout.component.Dock
52986  * @extends Ext.layout.component.AbstractDock
52987  * @private
52988  */
52989 Ext.define('Ext.layout.component.Dock', {
52990
52991     /* Begin Definitions */
52992
52993     alias: ['layout.dock'],
52994
52995     extend: 'Ext.layout.component.AbstractDock'
52996
52997     /* End Definitions */
52998
52999 });
53000 /**
53001  * @class Ext.panel.Panel
53002  * @extends Ext.panel.AbstractPanel
53003  * <p>Panel is a container that has specific functionality and structural components that make
53004  * it the perfect building block for application-oriented user interfaces.</p>
53005  * <p>Panels are, by virtue of their inheritance from {@link Ext.container.Container}, capable
53006  * of being configured with a {@link Ext.container.Container#layout layout}, and containing child Components.</p>
53007  * <p>When either specifying child {@link Ext.Component#items items} of a Panel, or dynamically {@link Ext.container.Container#add adding} Components
53008  * to a Panel, remember to consider how you wish the Panel to arrange those child elements, and whether
53009  * 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
53010  * default, Panels use the {@link Ext.layout.container.Auto Auto} scheme. This simply renders
53011  * child components, appending them one after the other inside the Container, and <b>does not apply any sizing</b>
53012  * at all.</p>
53013  * {@img Ext.panel.Panel/panel.png Panel components}
53014  * <p>A Panel may also contain {@link #bbar bottom} and {@link #tbar top} toolbars, along with separate
53015  * {@link #header}, {@link #footer} and {@link #body} sections (see {@link #frame} for additional
53016  * information).</p>
53017  * <p>Panel also provides built-in {@link #collapsible collapsible, expandable} and {@link #closable} behavior.
53018  * Panels can be easily dropped into any {@link Ext.container.Container Container} or layout, and the
53019  * layout and rendering pipeline is {@link Ext.container.Container#add completely managed by the framework}.</p>
53020  * <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
53021  * and the destruction of any descendant Components. This makes the Panel object, and all its descendants <b>unusable</b>. To enable the close
53022  * 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>
53023  * <p>Usually, Panels are used as constituents within an application, in which case, they would be used as child items of Containers,
53024  * and would themselves use Ext.Components as child {@link #items}. However to illustrate simply rendering a Panel into the document,
53025  * here&#39;s how to do it:<pre><code>
53026 Ext.create('Ext.panel.Panel', {
53027     title: 'Hello',
53028     width: 200,
53029     html: '&lt;p&gt;World!&lt;/p&gt;',
53030     renderTo: document.body
53031 });
53032 </code></pre></p>
53033  * <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>
53034 var filterPanel = Ext.create('Ext.panel.Panel', {
53035     bodyPadding: 5,  // Don&#39;t want content to crunch against the borders
53036     title: 'Filters',
53037     items: [{
53038         xtype: 'datefield',
53039         fieldLabel: 'Start date'
53040     }, {
53041         xtype: 'datefield',
53042         fieldLabel: 'End date'
53043     }]
53044 });
53045 </code></pre></p>
53046  * <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,
53047  * the Container into which the Panel is added will use a {@link #layout} to render, size and position its child Components.</p>
53048  * <p>Panels will often use specific {@link #layout}s to provide an application with shape and structure by containing and arranging child
53049  * Components: <pre><code>
53050 var resultsPanel = Ext.create('Ext.panel.Panel', {
53051     title: 'Results',
53052     width: 600,
53053     height: 400,
53054     renderTo: document.body,
53055     layout: {
53056         type: 'vbox',       // Arrange child items vertically
53057         align: 'stretch',    // Each takes up full width
53058         padding: 5
53059     },
53060     items: [{               // Results grid specified as a config object with an xtype of 'grid'
53061         xtype: 'grid',
53062         columns: [{header: 'Column One'}],            // One header just for show. There&#39;s no data,
53063         store: Ext.create('Ext.data.ArrayStore', {}), // A dummy empty data store
53064         flex: 1                                       // Use 1/3 of Container&#39;s height (hint to Box layout)
53065     }, {
53066         xtype: 'splitter'   // A splitter between the two child items
53067     }, {                    // Details Panel specified as a config object (no xtype defaults to 'panel').
53068         title: 'Details',
53069         bodyPadding: 5,
53070         items: [{
53071             fieldLabel: 'Data item',
53072             xtype: 'textfield'
53073         }], // An array of form fields
53074         flex: 2             // Use 2/3 of Container&#39;s height (hint to Box layout)
53075     }]
53076 });
53077 </code></pre>
53078  * The example illustrates one possible method of displaying search results. The Panel contains a grid with the resulting data arranged
53079  * in rows. Each selected row may be displayed in detail in the Panel below. The {@link Ext.layout.container.VBox vbox} layout is used
53080  * to arrange the two vertically. It is configured to stretch child items horizontally to full width. Child items may either be configured
53081  * with a numeric height, or with a <code>flex</code> value to distribute available space proportionately.</p>
53082  * <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
53083  * content area.</p>
53084  * <p>Using these techniques, as long as the <b>layout</b> is chosen and configured correctly, an application may have any level of
53085  * nested containment, all dynamically sized according to configuration, the user&#39;s preference and available browser size.</p>
53086  * @constructor
53087  * @param {Object} config The config object
53088  * @xtype panel
53089  */
53090 Ext.define('Ext.panel.Panel', {
53091     extend: 'Ext.panel.AbstractPanel',
53092     requires: [
53093         'Ext.panel.Header',
53094         'Ext.fx.Anim',
53095         'Ext.util.KeyMap',
53096         'Ext.panel.DD',
53097         'Ext.XTemplate',
53098         'Ext.layout.component.Dock'
53099     ],
53100     alias: 'widget.panel',
53101     alternateClassName: 'Ext.Panel',
53102
53103     /**
53104      * @cfg {String} collapsedCls
53105      * A CSS class to add to the panel&#39;s element after it has been collapsed (defaults to
53106      * <code>'collapsed'</code>).
53107      */
53108     collapsedCls: 'collapsed',
53109
53110     /**
53111      * @cfg {Boolean} animCollapse
53112      * <code>true</code> to animate the transition when the panel is collapsed, <code>false</code> to skip the
53113      * animation (defaults to <code>true</code> if the {@link Ext.fx.Anim} class is available, otherwise <code>false</code>).
53114      * May also be specified as the animation duration in milliseconds.
53115      */
53116     animCollapse: Ext.enableFx,
53117
53118     /**
53119      * @cfg {Number} minButtonWidth
53120      * Minimum width of all footer toolbar buttons in pixels (defaults to <tt>75</tt>). If set, this will
53121      * be used as the default value for the <tt>{@link Ext.button.Button#minWidth}</tt> config of
53122      * each Button added to the <b>footer toolbar</b> via the {@link #fbar} or {@link #buttons} configurations.
53123      * It will be ignored for buttons that have a minWidth configured some other way, e.g. in their own config
53124      * object or via the {@link Ext.container.Container#config-defaults defaults} of their parent container.
53125      */
53126     minButtonWidth: 75,
53127
53128     /**
53129      * @cfg {Boolean} collapsed
53130      * <code>true</code> to render the panel collapsed, <code>false</code> to render it expanded (defaults to
53131      * <code>false</code>).
53132      */
53133     collapsed: false,
53134
53135     /**
53136      * @cfg {Boolean} collapseFirst
53137      * <code>true</code> to make sure the collapse/expand toggle button always renders first (to the left of)
53138      * any other tools in the panel&#39;s title bar, <code>false</code> to render it last (defaults to <code>true</code>).
53139      */
53140     collapseFirst: true,
53141
53142     /**
53143      * @cfg {Boolean} hideCollapseTool
53144      * <code>true</code> to hide the expand/collapse toggle button when <code>{@link #collapsible} == true</code>,
53145      * <code>false</code> to display it (defaults to <code>false</code>).
53146      */
53147     hideCollapseTool: false,
53148
53149     /**
53150      * @cfg {Boolean} titleCollapse
53151      * <code>true</code> to allow expanding and collapsing the panel (when <code>{@link #collapsible} = true</code>)
53152      * by clicking anywhere in the header bar, <code>false</code>) to allow it only by clicking to tool button
53153      * (defaults to <code>false</code>)).
53154      */
53155     titleCollapse: false,
53156
53157     /**
53158      * @cfg {String} collapseMode
53159      * <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>
53160      * <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.
53161      * 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>
53162      * <p>When a child item of a {@link Ext.layout.container.Border border layout}, this config has two options:
53163      * <div class="mdetail-params"><ul>
53164      * <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
53165      * and to provide a UI with a Tool to allow the user to re-expand the Panel.</div></li>
53166      * <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>
53167      * </ul></div></p>
53168      */
53169
53170     /**
53171      * @cfg {Mixed} placeholder
53172      * <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}
53173      * when not using the <code>'header'</code> {@link #collapseMode}.</b></p>
53174      * <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
53175      * {@link Ext.layout.container.Border border layout}. Defaults to a generated {@link Ext.panel.Header Header}
53176      * containing a {@link Ext.panel.Tool Tool} to re-expand the Panel.</p>
53177      */
53178
53179     /**
53180      * @cfg {Boolean} floatable
53181      * <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>
53182      * <tt>true</tt> to allow clicking a collapsed Panel&#39;s {@link #placeholder} to display the Panel floated
53183      * above the layout, <tt>false</tt> to force the user to fully expand a collapsed region by
53184      * clicking the expand button to see it again (defaults to <tt>true</tt>).
53185      */
53186     floatable: true,
53187
53188     /**
53189      * @cfg {Boolean} collapsible
53190      * <p>True to make the panel collapsible and have an expand/collapse toggle Tool added into
53191      * 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>
53192      * See {@link #collapseMode} and {@link #collapseDirection}
53193      */
53194     collapsible: false,
53195
53196     /**
53197      * @cfg {Boolean} collapseDirection
53198      * <p>The direction to collapse the Panel when the toggle button is clicked.</p>
53199      * <p>Defaults to the {@link #headerPosition}</p>
53200      * <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>
53201      * <p>Specify as <code>'top'</code>, <code>'bottom'</code>, <code>'left'</code> or <code>'right'</code>.</p>
53202      */
53203
53204     /**
53205      * @cfg {Boolean} closable
53206      * <p>True to display the 'close' tool button and allow the user to close the window, false to
53207      * hide the button and disallow closing the window (defaults to <code>false</code>).</p>
53208      * <p>By default, when close is requested by clicking the close button in the header, the {@link #close}
53209      * method will be called. This will <i>{@link Ext.Component#destroy destroy}</i> the Panel and its content
53210      * meaning that it may not be reused.</p>
53211      * <p>To make closing a Panel <i>hide</i> the Panel so that it may be reused, set
53212      * {@link #closeAction} to 'hide'.</p>
53213      */
53214     closable: false,
53215
53216     /**
53217      * @cfg {String} closeAction
53218      * <p>The action to take when the close header tool is clicked:
53219      * <div class="mdetail-params"><ul>
53220      * <li><b><code>'{@link #destroy}'</code></b> : <b>Default</b><div class="sub-desc">
53221      * {@link #destroy remove} the window from the DOM and {@link Ext.Component#destroy destroy}
53222      * it and all descendant Components. The window will <b>not</b> be available to be
53223      * redisplayed via the {@link #show} method.
53224      * </div></li>
53225      * <li><b><code>'{@link #hide}'</code></b> : <div class="sub-desc">
53226      * {@link #hide} the window by setting visibility to hidden and applying negative offsets.
53227      * The window will be available to be redisplayed via the {@link #show} method.
53228      * </div></li>
53229      * </ul></div>
53230      * <p><b>Note:</b> This behavior has changed! setting *does* affect the {@link #close} method
53231      * which will invoke the approriate closeAction.
53232      */
53233     closeAction: 'destroy',
53234
53235     /**
53236      * @cfg {Object/Array} dockedItems
53237      * A component or series of components to be added as docked items to this panel.
53238      * The docked items can be docked to either the top, right, left or bottom of a panel.
53239      * This is typically used for things like toolbars or tab bars:
53240      * <pre><code>
53241 var panel = new Ext.panel.Panel({
53242     dockedItems: [{
53243         xtype: 'toolbar',
53244         dock: 'top',
53245         items: [{
53246             text: 'Docked to the top'
53247         }]
53248     }]
53249 });</pre></code>
53250      */
53251
53252     /**
53253       * @cfg {Boolean} preventHeader Prevent a Header from being created and shown. Defaults to false.
53254       */
53255     preventHeader: false,
53256
53257      /**
53258       * @cfg {String} headerPosition Specify as <code>'top'</code>, <code>'bottom'</code>, <code>'left'</code> or <code>'right'</code>. Defaults to <code>'top'</code>.
53259       */
53260     headerPosition: 'top',
53261
53262      /**
53263      * @cfg {Boolean} frame
53264      * True to apply a frame to the panel.
53265      */
53266     frame: false,
53267
53268     /**
53269      * @cfg {Boolean} frameHeader
53270      * True to apply a frame to the panel panels header (if 'frame' is true).
53271      */
53272     frameHeader: true,
53273
53274     /**
53275      * @cfg {Array} tools
53276      * An array of {@link Ext.panel.Tool} configs/instances to be added to the header tool area. The tools are stored as child
53277      * components of the header container. They can be accessed using {@link #down} and {#query}, as well as the other
53278      * component methods. The toggle tool is automatically created if {@link #collapsible} is set to true.
53279      * <p>Note that, apart from the toggle tool which is provided when a panel is collapsible, these
53280      * tools only provide the visual button. Any required functionality must be provided by adding
53281      * handlers that implement the necessary behavior.</p>
53282      * <p>Example usage:</p>
53283      * <pre><code>
53284 tools:[{
53285     type:'refresh',
53286     qtip: 'Refresh form Data',
53287     // hidden:true,
53288     handler: function(event, toolEl, panel){
53289         // refresh logic
53290     }
53291 },
53292 {
53293     type:'help',
53294     qtip: 'Get Help',
53295     handler: function(event, toolEl, panel){
53296         // show help here
53297     }
53298 }]
53299 </code></pre>
53300      */
53301
53302
53303     initComponent: function() {
53304         var me = this,
53305             cls;
53306
53307         me.addEvents(
53308         /**
53309          * @event titlechange
53310          * Fires after the Panel title has been set or changed.
53311          * @param {Ext.panel.Panel} p the Panel which has been resized.
53312          * @param {String} newTitle The new title.
53313          * @param {String} oldTitle The previous panel title.
53314          */
53315             'titlechange',
53316         /**
53317          * @event iconchange
53318          * Fires after the Panel iconCls has been set or changed.
53319          * @param {Ext.panel.Panel} p the Panel which has been resized.
53320          * @param {String} newIconCls The new iconCls.
53321          * @param {String} oldIconCls The previous panel iconCls.
53322          */
53323             'iconchange'
53324         );
53325
53326         if (me.unstyled) {
53327             me.setUI('plain');
53328         }
53329
53330         if (me.frame) {
53331             me.setUI('default-framed');
53332         }
53333
53334         me.callParent();
53335
53336         me.collapseDirection = me.collapseDirection || me.headerPosition || Ext.Component.DIRECTION_TOP;
53337
53338         // Backwards compatibility
53339         me.bridgeToolbars();
53340     },
53341
53342     setBorder: function(border) {
53343         // var me     = this,
53344         //     method = (border === false || border === 0) ? 'addClsWithUI' : 'removeClsWithUI';
53345         // 
53346         // me.callParent(arguments);
53347         // 
53348         // if (me.collapsed) {
53349         //     me[method](me.collapsedCls + '-noborder');
53350         // }
53351         // 
53352         // if (me.header) {
53353         //     me.header.setBorder(border);
53354         //     if (me.collapsed) {
53355         //         me.header[method](me.collapsedCls + '-noborder');
53356         //     }
53357         // }
53358         
53359         this.callParent(arguments);
53360     },
53361
53362     beforeDestroy: function() {
53363         Ext.destroy(
53364             this.ghostPanel,
53365             this.dd
53366         );
53367         this.callParent();
53368     },
53369
53370     initAria: function() {
53371         this.callParent();
53372         this.initHeaderAria();
53373     },
53374
53375     initHeaderAria: function() {
53376         var me = this,
53377             el = me.el,
53378             header = me.header;
53379         if (el && header) {
53380             el.dom.setAttribute('aria-labelledby', header.titleCmp.id);
53381         }
53382     },
53383
53384     getHeader: function() {
53385         return this.header;
53386     },
53387
53388     /**
53389      * Set a title for the panel&#39;s header. See {@link Ext.panel.Header#title}.
53390      * @param {String} newTitle
53391      */
53392     setTitle: function(newTitle) {
53393         var me = this,
53394         oldTitle = this.title;
53395
53396         me.title = newTitle;
53397         if (me.header) {
53398             me.header.setTitle(newTitle);
53399         } else {
53400             me.updateHeader();
53401         }
53402
53403         if (me.reExpander) {
53404             me.reExpander.setTitle(newTitle);
53405         }
53406         me.fireEvent('titlechange', me, newTitle, oldTitle);
53407     },
53408
53409     /**
53410      * Set the iconCls for the panel&#39;s header. See {@link Ext.panel.Header#iconCls}.
53411      * @param {String} newIconCls
53412      */
53413     setIconCls: function(newIconCls) {
53414         var me = this,
53415             oldIconCls = me.iconCls;
53416
53417         me.iconCls = newIconCls;
53418         var header = me.header;
53419         if (header) {
53420             header.setIconCls(newIconCls);
53421         }
53422         me.fireEvent('iconchange', me, newIconCls, oldIconCls);
53423     },
53424
53425     bridgeToolbars: function() {
53426         var me = this,
53427             fbar,
53428             fbarDefaults,
53429             minButtonWidth = me.minButtonWidth;
53430
53431         function initToolbar (toolbar, pos) {
53432             if (Ext.isArray(toolbar)) {
53433                 toolbar = {
53434                     xtype: 'toolbar',
53435                     items: toolbar
53436                 };
53437             }
53438             else if (!toolbar.xtype) {
53439                 toolbar.xtype = 'toolbar';
53440             }
53441             toolbar.dock = pos;
53442             if (pos == 'left' || pos == 'right') {
53443                 toolbar.vertical = true;
53444             }
53445             return toolbar;
53446         }
53447
53448         // Backwards compatibility
53449
53450         /**
53451          * @cfg {Object/Array} tbar
53452
53453 Convenience method. Short for 'Top Bar'.
53454
53455     tbar: [
53456       { xtype: 'button', text: 'Button 1' }
53457     ]
53458
53459 is equivalent to
53460
53461     dockedItems: [{
53462         xtype: 'toolbar',
53463         dock: 'top',
53464         items: [
53465             { xtype: 'button', text: 'Button 1' }
53466         ]
53467     }]
53468
53469          * @markdown
53470          */
53471         if (me.tbar) {
53472             me.addDocked(initToolbar(me.tbar, 'top'));
53473             me.tbar = null;
53474         }
53475
53476         /**
53477          * @cfg {Object/Array} bbar
53478
53479 Convenience method. Short for 'Bottom Bar'.
53480
53481     bbar: [
53482       { xtype: 'button', text: 'Button 1' }
53483     ]
53484
53485 is equivalent to
53486
53487     dockedItems: [{
53488         xtype: 'toolbar',
53489         dock: 'bottom',
53490         items: [
53491             { xtype: 'button', text: 'Button 1' }
53492         ]
53493     }]
53494
53495          * @markdown
53496          */
53497         if (me.bbar) {
53498             me.addDocked(initToolbar(me.bbar, 'bottom'));
53499             me.bbar = null;
53500         }
53501
53502         /**
53503          * @cfg {Object/Array} buttons
53504
53505 Convenience method used for adding buttons docked to the bottom right of the panel. This is a
53506 synonym for the {@link #fbar} config.
53507
53508     buttons: [
53509       { text: 'Button 1' }
53510     ]
53511
53512 is equivalent to
53513
53514     dockedItems: [{
53515         xtype: 'toolbar',
53516         dock: 'bottom',
53517         defaults: {minWidth: {@link #minButtonWidth}},
53518         items: [
53519             { xtype: 'component', flex: 1 },
53520             { xtype: 'button', text: 'Button 1' }
53521         ]
53522     }]
53523
53524 The {@link #minButtonWidth} is used as the default {@link Ext.button.Button#minWidth minWidth} for
53525 each of the buttons in the buttons toolbar.
53526
53527          * @markdown
53528          */
53529         if (me.buttons) {
53530             me.fbar = me.buttons;
53531             me.buttons = null;
53532         }
53533
53534         /**
53535          * @cfg {Object/Array} fbar
53536
53537 Convenience method used for adding items to the bottom right of the panel. Short for Footer Bar.
53538
53539     fbar: [
53540       { type: 'button', text: 'Button 1' }
53541     ]
53542
53543 is equivalent to
53544
53545     dockedItems: [{
53546         xtype: 'toolbar',
53547         dock: 'bottom',
53548         defaults: {minWidth: {@link #minButtonWidth}},
53549         items: [
53550             { xtype: 'component', flex: 1 },
53551             { xtype: 'button', text: 'Button 1' }
53552         ]
53553     }]
53554
53555 The {@link #minButtonWidth} is used as the default {@link Ext.button.Button#minWidth minWidth} for
53556 each of the buttons in the fbar.
53557
53558          * @markdown
53559          */
53560         if (me.fbar) {
53561             fbar = initToolbar(me.fbar, 'bottom');
53562             fbar.ui = 'footer';
53563
53564             // Apply the minButtonWidth config to buttons in the toolbar
53565             if (minButtonWidth) {
53566                 fbarDefaults = fbar.defaults;
53567                 fbar.defaults = function(config) {
53568                     var defaults = fbarDefaults || {};
53569                     if ((!config.xtype || config.xtype === 'button' || (config.isComponent && config.isXType('button'))) &&
53570                             !('minWidth' in defaults)) {
53571                         defaults = Ext.apply({minWidth: minButtonWidth}, defaults);
53572                     }
53573                     return defaults;
53574                 };
53575             }
53576
53577             fbar = me.addDocked(fbar)[0];
53578             fbar.insert(0, {
53579                 flex: 1,
53580                 xtype: 'component',
53581                 focusable: false
53582             });
53583             me.fbar = null;
53584         }
53585
53586         /**
53587          * @cfg {Object/Array} lbar
53588          *
53589          * Convenience method. Short for 'Left Bar' (left-docked, vertical toolbar).
53590          *
53591          *    lbar: [
53592          *      { xtype: 'button', text: 'Button 1' }
53593          *    ]
53594          *
53595          * is equivalent to
53596          *
53597          *    dockedItems: [{
53598          *        xtype: 'toolbar',
53599          *        dock: 'left',
53600          *        items: [
53601          *            { xtype: 'button', text: 'Button 1' }
53602          *        ]
53603          *    }]
53604          *
53605          * @markdown
53606          */
53607         if (me.lbar) {
53608             me.addDocked(initToolbar(me.lbar, 'left'));
53609             me.lbar = null;
53610         }
53611
53612         /**
53613          * @cfg {Object/Array} rbar
53614          *
53615          * Convenience method. Short for 'Right Bar' (right-docked, vertical toolbar).
53616          *
53617          *    rbar: [
53618          *      { xtype: 'button', text: 'Button 1' }
53619          *    ]
53620          *
53621          * is equivalent to
53622          *
53623          *    dockedItems: [{
53624          *        xtype: 'toolbar',
53625          *        dock: 'right',
53626          *        items: [
53627          *            { xtype: 'button', text: 'Button 1' }
53628          *        ]
53629          *    }]
53630          *
53631          * @markdown
53632          */
53633         if (me.rbar) {
53634             me.addDocked(initToolbar(me.rbar, 'right'));
53635             me.rbar = null;
53636         }
53637     },
53638
53639     /**
53640      * @private
53641      * Tools are a Panel-specific capabilty.
53642      * Panel uses initTools. Subclasses may contribute tools by implementing addTools.
53643      */
53644     initTools: function() {
53645         var me = this;
53646
53647         me.tools = me.tools || [];
53648
53649         // Add a collapse tool unless configured to not show a collapse tool
53650         // or to not even show a header.
53651         if (me.collapsible && !(me.hideCollapseTool || me.header === false)) {
53652             me.collapseDirection = me.collapseDirection || me.headerPosition || 'top';
53653             me.collapseTool = me.expandTool = me.createComponent({
53654                 xtype: 'tool',
53655                 type: 'collapse-' + me.collapseDirection,
53656                 expandType: me.getOppositeDirection(me.collapseDirection),
53657                 handler: me.toggleCollapse,
53658                 scope: me
53659             });
53660
53661             // Prepend collapse tool is configured to do so.
53662             if (me.collapseFirst) {
53663                 me.tools.unshift(me.collapseTool);
53664             }
53665         }
53666
53667         // Add subclass-specific tools.
53668         me.addTools();
53669
53670         // Make Panel closable.
53671         if (me.closable) {
53672             me.addClsWithUI('closable');
53673             me.addTool({
53674                 type: 'close',
53675                 handler: Ext.Function.bind(me.close, this, [])
53676             });
53677         }
53678
53679         // Append collapse tool if needed.
53680         if (me.collapseTool && !me.collapseFirst) {
53681             me.tools.push(me.collapseTool);
53682         }
53683     },
53684
53685     /**
53686      * @private
53687      * Template method to be implemented in subclasses to add their tools after the collapsible tool.
53688      */
53689     addTools: Ext.emptyFn,
53690
53691     /**
53692      * <p>Closes the Panel. By default, this method, removes it from the DOM, {@link Ext.Component#destroy destroy}s
53693      * the Panel object and all its descendant Components. The {@link #beforeclose beforeclose}
53694      * event is fired before the close happens and will cancel the close action if it returns false.<p>
53695      * <p><b>Note:</b> This method is not affected by the {@link #closeAction} setting which
53696      * only affects the action triggered when clicking the {@link #closable 'close' tool in the header}.
53697      * To hide the Panel without destroying it, call {@link #hide}.</p>
53698      */
53699     close: function() {
53700         if (this.fireEvent('beforeclose', this) !== false) {
53701             this.doClose();
53702         }
53703     },
53704
53705     // private
53706     doClose: function() {
53707         this.fireEvent('close', this);
53708         this[this.closeAction]();
53709     },
53710
53711     onRender: function(ct, position) {
53712         var me = this,
53713             topContainer;
53714
53715         // Add class-specific header tools.
53716         // Panel adds collapsible and closable.
53717         me.initTools();
53718
53719         // Dock the header/title
53720         me.updateHeader();
53721
53722         // If initially collapsed, collapsed flag must indicate true current state at this point.
53723         // Do collapse after the first time the Panel's structure has been laid out.
53724         if (me.collapsed) {
53725             me.collapsed = false;
53726             topContainer = me.findLayoutController();
53727             if (!me.hidden && topContainer) {
53728                 topContainer.on({
53729                     afterlayout: function() {
53730                         me.collapse(null, false, true);
53731                     },
53732                     single: true
53733                 });
53734             } else {
53735                 me.afterComponentLayout = function() {
53736                     delete me.afterComponentLayout;
53737                     Ext.getClass(me).prototype.afterComponentLayout.apply(me, arguments);
53738                     me.collapse(null, false, true);
53739                 };
53740             }
53741         }
53742
53743         // Call to super after adding the header, to prevent an unnecessary re-layout
53744         me.callParent(arguments);
53745     },
53746
53747     /**
53748      * Create, hide, or show the header component as appropriate based on the current config.
53749      * @private
53750      * @param {Boolean} force True to force the the header to be created
53751      */
53752     updateHeader: function(force) {
53753         var me = this,
53754             header = me.header,
53755             title = me.title,
53756             tools = me.tools;
53757
53758         if (!me.preventHeader && (force || title || (tools && tools.length))) {
53759             if (!header) {
53760                 header = me.header = Ext.create('Ext.panel.Header', {
53761                     title       : title,
53762                     orientation : (me.headerPosition == 'left' || me.headerPosition == 'right') ? 'vertical' : 'horizontal',
53763                     dock        : me.headerPosition || 'top',
53764                     textCls     : me.headerTextCls,
53765                     iconCls     : me.iconCls,
53766                     baseCls     : me.baseCls + '-header',
53767                     tools       : tools,
53768                     ui          : me.ui,
53769                     indicateDrag: me.draggable,
53770                     border      : me.border,
53771                     frame       : me.frame && me.frameHeader,
53772                     ignoreParentFrame : me.frame || me.overlapHeader,
53773                     ignoreBorderManagement: me.frame || me.ignoreHeaderBorderManagement,
53774                     listeners   : me.collapsible && me.titleCollapse ? {
53775                         click: me.toggleCollapse,
53776                         scope: me
53777                     } : null
53778                 });
53779                 me.addDocked(header, 0);
53780
53781                 // Reference the Header's tool array.
53782                 // Header injects named references.
53783                 me.tools = header.tools;
53784             }
53785             header.show();
53786             me.initHeaderAria();
53787         } else if (header) {
53788             header.hide();
53789         }
53790     },
53791
53792     // inherit docs
53793     setUI: function(ui) {
53794         var me = this;
53795
53796         me.callParent(arguments);
53797
53798         if (me.header) {
53799             me.header.setUI(ui);
53800         }
53801     },
53802
53803     // private
53804     getContentTarget: function() {
53805         return this.body;
53806     },
53807
53808     getTargetEl: function() {
53809         return this.body || this.frameBody || this.el;
53810     },
53811
53812     addTool: function(tool) {
53813         this.tools.push(tool);
53814         var header = this.header;
53815         if (header) {
53816             header.addTool(tool);
53817         }
53818         this.updateHeader();
53819     },
53820
53821     getOppositeDirection: function(d) {
53822         var c = Ext.Component;
53823         switch (d) {
53824             case c.DIRECTION_TOP:
53825                 return c.DIRECTION_BOTTOM;
53826             case c.DIRECTION_RIGHT:
53827                 return c.DIRECTION_LEFT;
53828             case c.DIRECTION_BOTTOM:
53829                 return c.DIRECTION_TOP;
53830             case c.DIRECTION_LEFT:
53831                 return c.DIRECTION_RIGHT;
53832         }
53833     },
53834
53835     /**
53836      * Collapses the panel body so that the body becomes hidden. Docked Components parallel to the
53837      * border towards which the collapse takes place will remain visible.  Fires the {@link #beforecollapse} event which will
53838      * cancel the collapse action if it returns false.
53839      * @param {Number} direction. The direction to collapse towards. Must be one of<ul>
53840      * <li>Ext.Component.DIRECTION_TOP</li>
53841      * <li>Ext.Component.DIRECTION_RIGHT</li>
53842      * <li>Ext.Component.DIRECTION_BOTTOM</li>
53843      * <li>Ext.Component.DIRECTION_LEFT</li></ul>
53844      * @param {Boolean} animate True to animate the transition, else false (defaults to the value of the
53845      * {@link #animCollapse} panel config)
53846      * @return {Ext.panel.Panel} this
53847      */
53848     collapse: function(direction, animate, /* private - passed if called at render time */ internal) {
53849         var me = this,
53850             c = Ext.Component,
53851             height = me.getHeight(),
53852             width = me.getWidth(),
53853             frameInfo,
53854             newSize = 0,
53855             dockedItems = me.dockedItems.items,
53856             dockedItemCount = dockedItems.length,
53857             i = 0,
53858             comp,
53859             pos,
53860             anim = {
53861                 from: {
53862                     height: height,
53863                     width: width
53864                 },
53865                 to: {
53866                     height: height,
53867                     width: width
53868                 },
53869                 listeners: {
53870                     afteranimate: me.afterCollapse,
53871                     scope: me
53872                 },
53873                 duration: Ext.Number.from(animate, Ext.fx.Anim.prototype.duration)
53874             },
53875             reExpander,
53876             reExpanderOrientation,
53877             reExpanderDock,
53878             getDimension,
53879             setDimension,
53880             collapseDimension;
53881
53882         if (!direction) {
53883             direction = me.collapseDirection;
53884         }
53885
53886         // If internal (Called because of initial collapsed state), then no animation, and no events.
53887         if (internal) {
53888             animate = false;
53889         } else if (me.collapsed || me.fireEvent('beforecollapse', me, direction, animate) === false) {
53890             return false;
53891         }
53892
53893         reExpanderDock = direction;
53894         me.expandDirection = me.getOppositeDirection(direction);
53895
53896         // Track docked items which we hide during collapsed state
53897         me.hiddenDocked = [];
53898
53899         switch (direction) {
53900             case c.DIRECTION_TOP:
53901             case c.DIRECTION_BOTTOM:
53902                 me.expandedSize = me.getHeight();
53903                 reExpanderOrientation = 'horizontal';
53904                 collapseDimension = 'height';
53905                 getDimension = 'getHeight';
53906                 setDimension = 'setHeight';
53907
53908                 // Collect the height of the visible header.
53909                 // Hide all docked items except the header.
53910                 // Hide *ALL* docked items if we're going to end up hiding the whole Panel anyway
53911                 for (; i < dockedItemCount; i++) {
53912                     comp = dockedItems[i];
53913                     if (comp.isVisible()) {
53914                         if (comp.isHeader && (!comp.dock || comp.dock == 'top' || comp.dock == 'bottom')) {
53915                             reExpander = comp;
53916                         } else {
53917                             me.hiddenDocked.push(comp);
53918                         }
53919                     }
53920                 }
53921
53922                 if (direction == Ext.Component.DIRECTION_BOTTOM) {
53923                     pos = me.getPosition()[1] - Ext.fly(me.el.dom.offsetParent).getRegion().top;
53924                     anim.from.top = pos;
53925                 }
53926                 break;
53927
53928             case c.DIRECTION_LEFT:
53929             case c.DIRECTION_RIGHT:
53930                 me.expandedSize = me.getWidth();
53931                 reExpanderOrientation = 'vertical';
53932                 collapseDimension = 'width';
53933                 getDimension = 'getWidth';
53934                 setDimension = 'setWidth';
53935
53936                 // Collect the height of the visible header.
53937                 // Hide all docked items except the header.
53938                 // Hide *ALL* docked items if we're going to end up hiding the whole Panel anyway
53939                 for (; i < dockedItemCount; i++) {
53940                     comp = dockedItems[i];
53941                     if (comp.isVisible()) {
53942                         if (comp.isHeader && (comp.dock == 'left' || comp.dock == 'right')) {
53943                             reExpander = comp;
53944                         } else {
53945                             me.hiddenDocked.push(comp);
53946                         }
53947                     }
53948                 }
53949
53950                 if (direction == Ext.Component.DIRECTION_RIGHT) {
53951                     pos = me.getPosition()[0] - Ext.fly(me.el.dom.offsetParent).getRegion().left;
53952                     anim.from.left = pos;
53953                 }
53954                 break;
53955
53956             default:
53957                 throw('Panel collapse must be passed a valid Component collapse direction');
53958         }
53959
53960         // No scrollbars when we shrink this Panel
53961         // And no laying out of any children... we're effectively *hiding* the body
53962         me.setAutoScroll(false);
53963         me.suspendLayout = true;
53964         me.body.setVisibilityMode(Ext.core.Element.DISPLAY);
53965
53966         // Disable toggle tool during animated collapse
53967         if (animate && me.collapseTool) {
53968             me.collapseTool.disable();
53969         }
53970
53971         // Add the collapsed class now, so that collapsed CSS rules are applied before measurements are taken.
53972         me.addClsWithUI(me.collapsedCls);
53973         // if (me.border === false) {
53974         //     me.addClsWithUI(me.collapsedCls + '-noborder');
53975         // }
53976
53977         // We found a header: Measure it to find the collapse-to size.
53978         if (reExpander) {
53979             //we must add the collapsed cls to the header and then remove to get the proper height
53980             reExpander.addClsWithUI(me.collapsedCls);
53981             reExpander.addClsWithUI(me.collapsedCls + '-' + reExpander.dock);
53982             if (me.border && (!me.frame || (me.frame && Ext.supports.CSS3BorderRadius))) {
53983                 reExpander.addClsWithUI(me.collapsedCls + '-border-' + reExpander.dock);
53984             }
53985
53986             frameInfo = reExpander.getFrameInfo();
53987                         
53988             //get the size
53989             newSize = reExpander[getDimension]() + (frameInfo ? frameInfo[direction] : 0);
53990
53991             //and remove
53992             reExpander.removeClsWithUI(me.collapsedCls);
53993             reExpander.removeClsWithUI(me.collapsedCls + '-' + reExpander.dock);              
53994             if (me.border && (!me.frame || (me.frame && Ext.supports.CSS3BorderRadius))) {
53995                 reExpander.removeClsWithUI(me.collapsedCls + '-border-' + reExpander.dock);
53996             }
53997         }
53998         // No header: Render and insert a temporary one, and then measure it.
53999         else {
54000             reExpander = {
54001                 hideMode: 'offsets',
54002                 temporary: true,
54003                 title: me.title,
54004                 orientation: reExpanderOrientation,
54005                 dock: reExpanderDock,
54006                 textCls: me.headerTextCls,
54007                 iconCls: me.iconCls,
54008                 baseCls: me.baseCls + '-header',
54009                 ui: me.ui,
54010                 frame: me.frame && me.frameHeader,
54011                 ignoreParentFrame: me.frame || me.overlapHeader,
54012                 indicateDrag: me.draggable,
54013                 cls: me.baseCls + '-collapsed-placeholder ' + ' ' + Ext.baseCSSPrefix + 'docked ' + me.baseCls + '-' + me.ui + '-collapsed',
54014                 renderTo: me.el
54015             };
54016             reExpander[(reExpander.orientation == 'horizontal') ? 'tools' : 'items'] = [{
54017                 xtype: 'tool',
54018                 type: 'expand-' + me.expandDirection,
54019                 handler: me.toggleCollapse,
54020                 scope: me
54021             }];
54022
54023             // Capture the size of the re-expander.
54024             // For vertical headers in IE6 and IE7, this will be sized by a CSS rule in _panel.scss
54025             reExpander = me.reExpander = Ext.create('Ext.panel.Header', reExpander);
54026             newSize = reExpander[getDimension]() + ((reExpander.frame) ? reExpander.frameSize[direction] : 0);
54027             reExpander.hide();
54028
54029             // Insert the new docked item
54030             me.insertDocked(0, reExpander);
54031         }
54032
54033         me.reExpander = reExpander;
54034         me.reExpander.addClsWithUI(me.collapsedCls);
54035         me.reExpander.addClsWithUI(me.collapsedCls + '-' + reExpander.dock);
54036         if (me.border && (!me.frame || (me.frame && Ext.supports.CSS3BorderRadius))) {
54037             me.reExpander.addClsWithUI(me.collapsedCls + '-border-' + me.reExpander.dock);
54038         }
54039
54040         // If collapsing right or down, we'll be also animating the left or top.
54041         if (direction == Ext.Component.DIRECTION_RIGHT) {
54042             anim.to.left = pos + (width - newSize);
54043         } else if (direction == Ext.Component.DIRECTION_BOTTOM) {
54044             anim.to.top = pos + (height - newSize);
54045         }
54046
54047         // Animate to the new size
54048         anim.to[collapseDimension] = newSize;
54049
54050         // Remove any flex config before we attempt to collapse.
54051         me.savedFlex = me.flex;
54052         me.savedMinWidth = me.minWidth;
54053         me.savedMinHeight = me.minHeight;
54054         me.minWidth = 0;
54055         me.minHeight = 0;
54056         delete me.flex;
54057
54058         if (animate) {
54059             me.animate(anim);
54060         } else {
54061             me.setSize(anim.to.width, anim.to.height);
54062             if (Ext.isDefined(anim.to.left) || Ext.isDefined(anim.to.top)) {
54063                 me.setPosition(anim.to.left, anim.to.top);
54064             }
54065             me.afterCollapse(false, internal);
54066         }
54067         return me;
54068     },
54069
54070     afterCollapse: function(animated, internal) {
54071         var me = this,
54072             i = 0,
54073             l = me.hiddenDocked.length;
54074
54075         me.minWidth = me.savedMinWidth;
54076         me.minHeight = me.savedMinHeight;
54077
54078         me.body.hide();
54079         for (; i < l; i++) {
54080             me.hiddenDocked[i].hide();
54081         }
54082         if (me.reExpander) {
54083             me.reExpander.updateFrame();
54084             me.reExpander.show();
54085         }
54086         me.collapsed = true;
54087
54088         if (!internal) {
54089             me.doComponentLayout();
54090         }
54091
54092         if (me.resizer) {
54093             me.resizer.disable();
54094         }
54095
54096         // If me Panel was configured with a collapse tool in its header, flip it's type
54097         if (me.collapseTool) {
54098             me.collapseTool.setType('expand-' + me.expandDirection);
54099         }
54100         if (!internal) {
54101             me.fireEvent('collapse', me);
54102         }
54103
54104         // Re-enable the toggle tool after an animated collapse
54105         if (animated && me.collapseTool) {
54106             me.collapseTool.enable();
54107         }
54108     },
54109
54110     /**
54111      * Expands the panel body so that it becomes visible.  Fires the {@link #beforeexpand} event which will
54112      * cancel the expand action if it returns false.
54113      * @param {Boolean} animate True to animate the transition, else false (defaults to the value of the
54114      * {@link #animCollapse} panel config)
54115      * @return {Ext.panel.Panel} this
54116      */
54117     expand: function(animate) {
54118         if (!this.collapsed || this.fireEvent('beforeexpand', this, animate) === false) {
54119             return false;
54120         }
54121         var me = this,
54122             i = 0,
54123             l = me.hiddenDocked.length,
54124             direction = me.expandDirection,
54125             height = me.getHeight(),
54126             width = me.getWidth(),
54127             pos, anim, satisfyJSLint;
54128
54129         // Disable toggle tool during animated expand
54130         if (animate && me.collapseTool) {
54131             me.collapseTool.disable();
54132         }
54133
54134         // Show any docked items that we hid on collapse
54135         // And hide the injected reExpander Header
54136         for (; i < l; i++) {
54137             me.hiddenDocked[i].hidden = false;
54138             me.hiddenDocked[i].el.show();
54139         }
54140         if (me.reExpander) {
54141             if (me.reExpander.temporary) {
54142                 me.reExpander.hide();
54143             } else {
54144                 me.reExpander.removeClsWithUI(me.collapsedCls);
54145                 me.reExpander.removeClsWithUI(me.collapsedCls + '-' + me.reExpander.dock);
54146                 if (me.border && (!me.frame || (me.frame && Ext.supports.CSS3BorderRadius))) {
54147                     me.reExpander.removeClsWithUI(me.collapsedCls + '-border-' + me.reExpander.dock);
54148                 }
54149                 me.reExpander.updateFrame();
54150             }
54151         }
54152
54153         // If me Panel was configured with a collapse tool in its header, flip it's type
54154         if (me.collapseTool) {
54155             me.collapseTool.setType('collapse-' + me.collapseDirection);
54156         }
54157
54158         // Unset the flag before the potential call to calculateChildBox to calculate our newly flexed size
54159         me.collapsed = false;
54160
54161         // Collapsed means body element was hidden
54162         me.body.show();
54163
54164         // Remove any collapsed styling before any animation begins
54165         me.removeClsWithUI(me.collapsedCls);
54166         // if (me.border === false) {
54167         //     me.removeClsWithUI(me.collapsedCls + '-noborder');
54168         // }
54169
54170         anim = {
54171             to: {
54172             },
54173             from: {
54174                 height: height,
54175                 width: width
54176             },
54177             listeners: {
54178                 afteranimate: me.afterExpand,
54179                 scope: me
54180             }
54181         };
54182
54183         if ((direction == Ext.Component.DIRECTION_TOP) || (direction == Ext.Component.DIRECTION_BOTTOM)) {
54184
54185             // If autoHeight, measure the height now we have shown the body element.
54186             if (me.autoHeight) {
54187                 me.setCalculatedSize(me.width, null);
54188                 anim.to.height = me.getHeight();
54189
54190                 // Must size back down to collapsed for the animation.
54191                 me.setCalculatedSize(me.width, anim.from.height);
54192             }
54193             // If we were flexed, then we can't just restore to the saved size.
54194             // We must restore to the currently correct, flexed size, so we much ask the Box layout what that is.
54195             else if (me.savedFlex) {
54196                 me.flex = me.savedFlex;
54197                 anim.to.height = me.ownerCt.layout.calculateChildBox(me).height;
54198                 delete me.flex;
54199             }
54200             // Else, restore to saved height
54201             else {
54202                 anim.to.height = me.expandedSize;
54203             }
54204
54205             // top needs animating upwards
54206             if (direction == Ext.Component.DIRECTION_TOP) {
54207                 pos = me.getPosition()[1] - Ext.fly(me.el.dom.offsetParent).getRegion().top;
54208                 anim.from.top = pos;
54209                 anim.to.top = pos - (anim.to.height - height);
54210             }
54211         } else if ((direction == Ext.Component.DIRECTION_LEFT) || (direction == Ext.Component.DIRECTION_RIGHT)) {
54212
54213             // If autoWidth, measure the width now we have shown the body element.
54214             if (me.autoWidth) {
54215                 me.setCalculatedSize(null, me.height);
54216                 anim.to.width = me.getWidth();
54217
54218                 // Must size back down to collapsed for the animation.
54219                 me.setCalculatedSize(anim.from.width, me.height);
54220             }
54221             // If we were flexed, then we can't just restore to the saved size.
54222             // We must restore to the currently correct, flexed size, so we much ask the Box layout what that is.
54223             else if (me.savedFlex) {
54224                 me.flex = me.savedFlex;
54225                 anim.to.width = me.ownerCt.layout.calculateChildBox(me).width;
54226                 delete me.flex;
54227             }
54228             // Else, restore to saved width
54229             else {
54230                 anim.to.width = me.expandedSize;
54231             }
54232
54233             // left needs animating leftwards
54234             if (direction == Ext.Component.DIRECTION_LEFT) {
54235                 pos = me.getPosition()[0] - Ext.fly(me.el.dom.offsetParent).getRegion().left;
54236                 anim.from.left = pos;
54237                 anim.to.left = pos - (anim.to.width - width);
54238             }
54239         }
54240
54241         if (animate) {
54242             me.animate(anim);
54243         } else {
54244             me.setSize(anim.to.width, anim.to.height);
54245             if (anim.to.x) {
54246                 me.setLeft(anim.to.x);
54247             }
54248             if (anim.to.y) {
54249                 me.setTop(anim.to.y);
54250             }
54251             me.afterExpand(false);
54252         }
54253
54254         return me;
54255     },
54256
54257     afterExpand: function(animated) {
54258         var me = this;
54259         me.setAutoScroll(me.initialConfig.autoScroll);
54260
54261         // Restored to a calculated flex. Delete the set width and height properties so that flex works from now on.
54262         if (me.savedFlex) {
54263             me.flex = me.savedFlex;
54264             delete me.savedFlex;
54265             delete me.width;
54266             delete me.height;
54267         }
54268
54269         // Reinstate layout out after Panel has re-expanded
54270         delete me.suspendLayout;
54271         if (animated && me.ownerCt) {
54272             me.ownerCt.doLayout();
54273         }
54274
54275         if (me.resizer) {
54276             me.resizer.enable();
54277         }
54278
54279         me.fireEvent('expand', me);
54280
54281         // Re-enable the toggle tool after an animated expand
54282         if (animated && me.collapseTool) {
54283             me.collapseTool.enable();
54284         }
54285     },
54286
54287     /**
54288      * Shortcut for performing an {@link #expand} or {@link #collapse} based on the current state of the panel.
54289      * @return {Ext.panel.Panel} this
54290      */
54291     toggleCollapse: function() {
54292         if (this.collapsed) {
54293             this.expand(this.animCollapse);
54294         } else {
54295             this.collapse(this.collapseDirection, this.animCollapse);
54296         }
54297         return this;
54298     },
54299
54300     // private
54301     getKeyMap : function(){
54302         if(!this.keyMap){
54303             this.keyMap = Ext.create('Ext.util.KeyMap', this.el, this.keys);
54304         }
54305         return this.keyMap;
54306     },
54307
54308     // private
54309     initDraggable : function(){
54310         /**
54311          * <p>If this Panel is configured {@link #draggable}, this property will contain
54312          * an instance of {@link Ext.dd.DragSource} which handles dragging the Panel.</p>
54313          * The developer must provide implementations of the abstract methods of {@link Ext.dd.DragSource}
54314          * in order to supply behaviour for each stage of the drag/drop process. See {@link #draggable}.
54315          * @type Ext.dd.DragSource.
54316          * @property dd
54317          */
54318         this.dd = Ext.create('Ext.panel.DD', this, Ext.isBoolean(this.draggable) ? null : this.draggable);
54319     },
54320
54321     // private - helper function for ghost
54322     ghostTools : function() {
54323         var tools = [],
54324             origTools = this.initialConfig.tools;
54325
54326         if (origTools) {
54327             Ext.each(origTools, function(tool) {
54328                 // Some tools can be full components, and copying them into the ghost
54329                 // actually removes them from the owning panel. You could also potentially
54330                 // end up with duplicate DOM ids as well. To avoid any issues we just make
54331                 // a simple bare-minimum clone of each tool for ghosting purposes.
54332                 tools.push({
54333                     type: tool.type
54334                 });
54335             });
54336         }
54337         else {
54338             tools = [{
54339                 type: 'placeholder'
54340             }];
54341         }
54342         return tools;
54343     },
54344
54345     // private - used for dragging
54346     ghost: function(cls) {
54347         var me = this,
54348             ghostPanel = me.ghostPanel,
54349             box = me.getBox();
54350
54351         if (!ghostPanel) {
54352             ghostPanel = Ext.create('Ext.panel.Panel', {
54353                 renderTo: document.body,
54354                 floating: {
54355                     shadow: false
54356                 },
54357                 frame: Ext.supports.CSS3BorderRadius ? me.frame : false,
54358                 title: me.title,
54359                 overlapHeader: me.overlapHeader,
54360                 headerPosition: me.headerPosition,
54361                 width: me.getWidth(),
54362                 height: me.getHeight(),
54363                 iconCls: me.iconCls,
54364                 baseCls: me.baseCls,
54365                 tools: me.ghostTools(),
54366                 cls: me.baseCls + '-ghost ' + (cls ||'')
54367             });
54368             me.ghostPanel = ghostPanel;
54369         }
54370         ghostPanel.floatParent = me.floatParent;
54371         if (me.floating) {
54372             ghostPanel.setZIndex(Ext.Number.from(me.el.getStyle('zIndex'), 0));
54373         } else {
54374             ghostPanel.toFront();
54375         }
54376         ghostPanel.el.show();
54377         ghostPanel.setPosition(box.x, box.y);
54378         ghostPanel.setSize(box.width, box.height);
54379         me.el.hide();
54380         if (me.floatingItems) {
54381             me.floatingItems.hide();
54382         }
54383         return ghostPanel;
54384     },
54385
54386     // private
54387     unghost: function(show, matchPosition) {
54388         var me = this;
54389         if (!me.ghostPanel) {
54390             return;
54391         }
54392         if (show !== false) {
54393             me.el.show();
54394             if (matchPosition !== false) {
54395                 me.setPosition(me.ghostPanel.getPosition());
54396             }
54397             if (me.floatingItems) {
54398                 me.floatingItems.show();
54399             }
54400             Ext.defer(me.focus, 10, me);
54401         }
54402         me.ghostPanel.el.hide();
54403     },
54404
54405     initResizable: function(resizable) {
54406         if (this.collapsed) {
54407             resizable.disabled = true;
54408         }
54409         this.callParent([resizable]);
54410     }
54411 });
54412
54413 /**
54414  * Component layout for Tip/ToolTip/etc. components
54415  * @class Ext.layout.component.Tip
54416  * @extends Ext.layout.component.Dock
54417  * @private
54418  */
54419
54420 Ext.define('Ext.layout.component.Tip', {
54421
54422     /* Begin Definitions */
54423
54424     alias: ['layout.tip'],
54425
54426     extend: 'Ext.layout.component.Dock',
54427
54428     /* End Definitions */
54429
54430     type: 'tip',
54431     
54432     onLayout: function(width, height) {
54433         var me = this,
54434             owner = me.owner,
54435             el = owner.el,
54436             minWidth,
54437             maxWidth,
54438             naturalWidth,
54439             constrainedWidth,
54440             xy = el.getXY();
54441
54442         // Position offscreen so the natural width is not affected by the viewport's right edge
54443         el.setXY([-9999,-9999]);
54444
54445         // Calculate initial layout
54446         this.callParent(arguments);
54447
54448         // Handle min/maxWidth for auto-width tips
54449         if (!Ext.isNumber(width)) {
54450             minWidth = owner.minWidth;
54451             maxWidth = owner.maxWidth;
54452             // IE6/7 in strict mode have a problem doing an autoWidth
54453             if (Ext.isStrict && (Ext.isIE6 || Ext.isIE7)) {
54454                 constrainedWidth = me.doAutoWidth();
54455             } else {
54456                 naturalWidth = el.getWidth();
54457             }
54458             if (naturalWidth < minWidth) {
54459                 constrainedWidth = minWidth;
54460             }
54461             else if (naturalWidth > maxWidth) {
54462                 constrainedWidth = maxWidth;
54463             }
54464             if (constrainedWidth) {
54465                 this.callParent([constrainedWidth, height]);
54466             }
54467         }
54468
54469         // Restore position
54470         el.setXY(xy);
54471     },
54472     
54473     doAutoWidth: function(){
54474         var me = this,
54475             owner = me.owner,
54476             body = owner.body,
54477             width = body.getTextWidth();
54478             
54479         if (owner.header) {
54480             width = Math.max(width, owner.header.getWidth());
54481         }
54482         if (!Ext.isDefined(me.frameWidth)) {
54483             me.frameWidth = owner.el.getWidth() - body.getWidth();
54484         }
54485         width += me.frameWidth + body.getPadding('lr');
54486         return width;
54487     }
54488 });
54489
54490 /**
54491  * @class Ext.tip.Tip
54492  * @extends Ext.panel.Panel
54493  * This is the base class for {@link Ext.tip.QuickTip} and {@link Ext.tip.ToolTip} that provides the basic layout and
54494  * positioning that all tip-based classes require. This class can be used directly for simple, statically-positioned
54495  * tips that are displayed programmatically, or it can be extended to provide custom tip implementations.
54496  * @constructor
54497  * Create a new Tip
54498  * @param {Object} config The configuration options
54499  * @xtype tip
54500  */
54501 Ext.define('Ext.tip.Tip', {
54502     extend: 'Ext.panel.Panel',
54503     requires: [ 'Ext.layout.component.Tip' ],
54504     alternateClassName: 'Ext.Tip',
54505     /**
54506      * @cfg {Boolean} closable True to render a close tool button into the tooltip header (defaults to false).
54507      */
54508     /**
54509      * @cfg {Number} width
54510      * Width in pixels of the tip (defaults to auto).  Width will be ignored if it exceeds the bounds of
54511      * {@link #minWidth} or {@link #maxWidth}.  The maximum supported value is 500.
54512      */
54513     /**
54514      * @cfg {Number} minWidth The minimum width of the tip in pixels (defaults to 40).
54515      */
54516     minWidth : 40,
54517     /**
54518      * @cfg {Number} maxWidth The maximum width of the tip in pixels (defaults to 300).  The maximum supported value is 500.
54519      */
54520     maxWidth : 300,
54521     /**
54522      * @cfg {Boolean/String} shadow True or "sides" for the default effect, "frame" for 4-way shadow, and "drop"
54523      * for bottom-right shadow (defaults to "sides").
54524      */
54525     shadow : "sides",
54526
54527     /**
54528      * @cfg {String} defaultAlign <b>Experimental</b>. The default {@link Ext.core.Element#alignTo} anchor position value
54529      * for this tip relative to its element of origin (defaults to "tl-bl?").
54530      */
54531     defaultAlign : "tl-bl?",
54532     /**
54533      * @cfg {Boolean} constrainPosition If true, then the tooltip will be automatically constrained to stay within
54534      * the browser viewport. Defaults to false.
54535      */
54536     constrainPosition : true,
54537
54538     /**
54539      * @inherited
54540      */
54541     frame: false,
54542
54543     // private panel overrides
54544     autoRender: true,
54545     hidden: true,
54546     baseCls: Ext.baseCSSPrefix + 'tip',
54547     floating: {
54548         shadow: true,
54549         shim: true,
54550         constrain: true
54551     },
54552     focusOnToFront: false,
54553     componentLayout: 'tip',
54554
54555     closeAction: 'hide',
54556
54557     ariaRole: 'tooltip',
54558
54559     initComponent: function() {
54560         this.callParent(arguments);
54561
54562         // Or in the deprecated config. Floating.doConstrain only constrains if the constrain property is truthy.
54563         this.constrain = this.constrain || this.constrainPosition;
54564     },
54565
54566     /**
54567      * Shows this tip at the specified XY position.  Example usage:
54568      * <pre><code>
54569 // Show the tip at x:50 and y:100
54570 tip.showAt([50,100]);
54571 </code></pre>
54572      * @param {Array} xy An array containing the x and y coordinates
54573      */
54574     showAt : function(xy){
54575         var me = this;
54576         this.callParent();
54577         // Show may have been vetoed.
54578         if (me.isVisible()) {
54579             me.setPagePosition(xy[0], xy[1]);
54580             if (me.constrainPosition || me.constrain) {
54581                 me.doConstrain();
54582             }
54583             me.toFront(true);
54584         }
54585     },
54586
54587     /**
54588      * <b>Experimental</b>. Shows this tip at a position relative to another element using a standard {@link Ext.core.Element#alignTo}
54589      * anchor position value.  Example usage:
54590      * <pre><code>
54591 // Show the tip at the default position ('tl-br?')
54592 tip.showBy('my-el');
54593
54594 // Show the tip's top-left corner anchored to the element's top-right corner
54595 tip.showBy('my-el', 'tl-tr');
54596 </code></pre>
54597      * @param {Mixed} el An HTMLElement, Ext.core.Element or string id of the target element to align to
54598      * @param {String} position (optional) A valid {@link Ext.core.Element#alignTo} anchor position (defaults to 'tl-br?' or
54599      * {@link #defaultAlign} if specified).
54600      */
54601     showBy : function(el, pos) {
54602         this.showAt(this.el.getAlignToXY(el, pos || this.defaultAlign));
54603     },
54604
54605     /**
54606      * @private
54607      * @override
54608      * Set Tip draggable using base Component's draggability
54609      */
54610     initDraggable : function(){
54611         var me = this;
54612         me.draggable = {
54613             el: me.getDragEl(),
54614             delegate: me.header.el,
54615             constrain: me,
54616             constrainTo: me.el.dom.parentNode
54617         };
54618         // Important: Bypass Panel's initDraggable. Call direct to Component's implementation.
54619         Ext.Component.prototype.initDraggable.call(me);
54620     },
54621
54622     // Tip does not ghost. Drag is "live"
54623     ghost: undefined,
54624     unghost: undefined
54625 });
54626
54627 /**
54628  * @class Ext.tip.ToolTip
54629  * @extends Ext.tip.Tip
54630  * 
54631  * ToolTip is a {@link Ext.tip.Tip} implementation that handles the common case of displaying a
54632  * tooltip when hovering over a certain element or elements on the page. It allows fine-grained
54633  * control over the tooltip's alignment relative to the target element or mouse, and the timing
54634  * of when it is automatically shown and hidden.
54635  * 
54636  * This implementation does **not** have a built-in method of automatically populating the tooltip's
54637  * text based on the target element; you must either configure a fixed {@link #html} value for each
54638  * ToolTip instance, or implement custom logic (e.g. in a {@link #beforeshow} event listener) to
54639  * generate the appropriate tooltip content on the fly. See {@link Ext.tip.QuickTip} for a more
54640  * convenient way of automatically populating and configuring a tooltip based on specific DOM
54641  * attributes of each target element.
54642  * 
54643  * ## Basic Example
54644  * 
54645  *     var tip = Ext.create('Ext.tip.ToolTip', {
54646  *         target: 'clearButton',
54647  *         html: 'Press this button to clear the form'
54648  *     });
54649  * 
54650  * {@img Ext.tip.ToolTip/Ext.tip.ToolTip1.png Basic Ext.tip.ToolTip}
54651  * 
54652  * ## Delegation
54653  * 
54654  * In addition to attaching a ToolTip to a single element, you can also use delegation to attach
54655  * one ToolTip to many elements under a common parent. This is more efficient than creating many
54656  * ToolTip instances. To do this, point the {@link #target} config to a common ancestor of all the
54657  * elements, and then set the {@link #delegate} config to a CSS selector that will select all the
54658  * appropriate sub-elements.
54659  * 
54660  * When using delegation, it is likely that you will want to programmatically change the content
54661  * of the ToolTip based on each delegate element; you can do this by implementing a custom
54662  * listener for the {@link #beforeshow} event. Example:
54663  * 
54664  *     var myGrid = Ext.create('Ext.grid.GridPanel', gridConfig);
54665  *     myGrid.on('render', function(grid) {
54666  *         var view = grid.getView();    // Capture the grid's view.
54667  *         grid.tip = Ext.create('Ext.tip.ToolTip', {
54668  *             target: view.el,          // The overall target element.
54669  *             delegate: view.itemSelector, // Each grid row causes its own seperate show and hide.
54670  *             trackMouse: true,         // Moving within the row should not hide the tip.
54671  *             renderTo: Ext.getBody(),  // Render immediately so that tip.body can be referenced prior to the first show.
54672  *             listeners: {              // Change content dynamically depending on which element triggered the show.
54673  *                 beforeshow: function updateTipBody(tip) {
54674  *                     tip.update('Over company "' + view.getRecord(tip.triggerElement).get('company') + '"');
54675  *                 }
54676  *             }
54677  *         });
54678  *     });
54679  * 
54680  * {@img Ext.tip.ToolTip/Ext.tip.ToolTip2.png Ext.tip.ToolTip with delegation}
54681  * 
54682  * ## Alignment
54683  * 
54684  * The following configuration properties allow control over how the ToolTip is aligned relative to
54685  * the target element and/or mouse pointer:
54686  * 
54687  *  - {@link #anchor}
54688  *  - {@link #anchorToTarget}
54689  *  - {@link #anchorOffset}
54690  *  - {@link #trackMouse}
54691  *  - {@link #mouseOffset}
54692  * 
54693  * ## Showing/Hiding
54694  * 
54695  * The following configuration properties allow control over how and when the ToolTip is automatically
54696  * shown and hidden:
54697  * 
54698  *  - {@link #autoHide}
54699  *  - {@link #showDelay}
54700  *  - {@link #hideDelay}
54701  *  - {@link #dismissDelay}
54702  * 
54703  * @constructor
54704  * Create a new ToolTip instance
54705  * @param {Object} config The configuration options
54706  * @xtype tooltip
54707  * @markdown
54708  * @docauthor Jason Johnston <jason@sencha.com>
54709  */
54710 Ext.define('Ext.tip.ToolTip', {
54711     extend: 'Ext.tip.Tip',
54712     alias: 'widget.tooltip',
54713     alternateClassName: 'Ext.ToolTip',
54714     /**
54715      * When a ToolTip is configured with the <code>{@link #delegate}</code>
54716      * option to cause selected child elements of the <code>{@link #target}</code>
54717      * Element to each trigger a seperate show event, this property is set to
54718      * the DOM element which triggered the show.
54719      * @type DOMElement
54720      * @property triggerElement
54721      */
54722     /**
54723      * @cfg {Mixed} target The target HTMLElement, Ext.core.Element or id to monitor
54724      * for mouseover events to trigger showing this ToolTip.
54725      */
54726     /**
54727      * @cfg {Boolean} autoHide True to automatically hide the tooltip after the
54728      * mouse exits the target element or after the <code>{@link #dismissDelay}</code>
54729      * has expired if set (defaults to true).  If <code>{@link #closable} = true</code>
54730      * a close tool button will be rendered into the tooltip header.
54731      */
54732     /**
54733      * @cfg {Number} showDelay Delay in milliseconds before the tooltip displays
54734      * after the mouse enters the target element (defaults to 500)
54735      */
54736     showDelay: 500,
54737     /**
54738      * @cfg {Number} hideDelay Delay in milliseconds after the mouse exits the
54739      * target element but before the tooltip actually hides (defaults to 200).
54740      * Set to 0 for the tooltip to hide immediately.
54741      */
54742     hideDelay: 200,
54743     /**
54744      * @cfg {Number} dismissDelay Delay in milliseconds before the tooltip
54745      * automatically hides (defaults to 5000). To disable automatic hiding, set
54746      * dismissDelay = 0.
54747      */
54748     dismissDelay: 5000,
54749     /**
54750      * @cfg {Array} mouseOffset An XY offset from the mouse position where the
54751      * tooltip should be shown (defaults to [15,18]).
54752      */
54753     /**
54754      * @cfg {Boolean} trackMouse True to have the tooltip follow the mouse as it
54755      * moves over the target element (defaults to false).
54756      */
54757     trackMouse: false,
54758     /**
54759      * @cfg {String} anchor If specified, indicates that the tip should be anchored to a
54760      * particular side of the target element or mouse pointer ("top", "right", "bottom",
54761      * or "left"), with an arrow pointing back at the target or mouse pointer. If
54762      * {@link #constrainPosition} is enabled, this will be used as a preferred value
54763      * only and may be flipped as needed.
54764      */
54765     /**
54766      * @cfg {Boolean} anchorToTarget True to anchor the tooltip to the target
54767      * element, false to anchor it relative to the mouse coordinates (defaults
54768      * to true).  When <code>anchorToTarget</code> is true, use
54769      * <code>{@link #defaultAlign}</code> to control tooltip alignment to the
54770      * target element.  When <code>anchorToTarget</code> is false, use
54771      * <code>{@link #anchorPosition}</code> instead to control alignment.
54772      */
54773     anchorToTarget: true,
54774     /**
54775      * @cfg {Number} anchorOffset A numeric pixel value used to offset the
54776      * default position of the anchor arrow (defaults to 0).  When the anchor
54777      * position is on the top or bottom of the tooltip, <code>anchorOffset</code>
54778      * will be used as a horizontal offset.  Likewise, when the anchor position
54779      * is on the left or right side, <code>anchorOffset</code> will be used as
54780      * a vertical offset.
54781      */
54782     anchorOffset: 0,
54783     /**
54784      * @cfg {String} delegate <p>Optional. A {@link Ext.DomQuery DomQuery}
54785      * selector which allows selection of individual elements within the
54786      * <code>{@link #target}</code> element to trigger showing and hiding the
54787      * ToolTip as the mouse moves within the target.</p>
54788      * <p>When specified, the child element of the target which caused a show
54789      * event is placed into the <code>{@link #triggerElement}</code> property
54790      * before the ToolTip is shown.</p>
54791      * <p>This may be useful when a Component has regular, repeating elements
54792      * in it, each of which need a ToolTip which contains information specific
54793      * to that element. For example:</p><pre><code>
54794 var myGrid = Ext.create('Ext.grid.GridPanel', gridConfig);
54795 myGrid.on('render', function(grid) {
54796     var view = grid.getView();    // Capture the grid's view.
54797     grid.tip = Ext.create('Ext.tip.ToolTip', {
54798         target: view.el,          // The overall target element.
54799         delegate: view.itemSelector, // Each grid row causes its own seperate show and hide.
54800         trackMouse: true,         // Moving within the row should not hide the tip.
54801         renderTo: Ext.getBody(),  // Render immediately so that tip.body can be referenced prior to the first show.
54802         listeners: {              // Change content dynamically depending on which element triggered the show.
54803             beforeshow: function(tip) {
54804                 tip.update('Over Record ID ' + view.getRecord(tip.triggerElement).id);
54805             }
54806         }
54807     });
54808 });
54809      *</code></pre>
54810      */
54811
54812     // private
54813     targetCounter: 0,
54814     quickShowInterval: 250,
54815
54816     // private
54817     initComponent: function() {
54818         var me = this;
54819         me.callParent(arguments);
54820         me.lastActive = new Date();
54821         me.setTarget(me.target);
54822         me.origAnchor = me.anchor;
54823     },
54824
54825     // private
54826     onRender: function(ct, position) {
54827         var me = this;
54828         me.callParent(arguments);
54829         me.anchorCls = Ext.baseCSSPrefix + 'tip-anchor-' + me.getAnchorPosition();
54830         me.anchorEl = me.el.createChild({
54831             cls: Ext.baseCSSPrefix + 'tip-anchor ' + me.anchorCls
54832         });
54833     },
54834
54835     // private
54836     afterRender: function() {
54837         var me = this,
54838             zIndex;
54839
54840         me.callParent(arguments);
54841         zIndex = parseInt(me.el.getZIndex(), 10) || 0;
54842         me.anchorEl.setStyle('z-index', zIndex + 1).setVisibilityMode(Ext.core.Element.DISPLAY);
54843     },
54844
54845     /**
54846      * Binds this ToolTip to the specified element. The tooltip will be displayed when the mouse moves over the element.
54847      * @param {Mixed} t The Element, HtmlElement, or ID of an element to bind to
54848      */
54849     setTarget: function(target) {
54850         var me = this,
54851             t = Ext.get(target),
54852             tg;
54853
54854         if (me.target) {
54855             tg = Ext.get(me.target);
54856             me.mun(tg, 'mouseover', me.onTargetOver, me);
54857             me.mun(tg, 'mouseout', me.onTargetOut, me);
54858             me.mun(tg, 'mousemove', me.onMouseMove, me);
54859         }
54860         
54861         me.target = t;
54862         if (t) {
54863             
54864             me.mon(t, {
54865                 // TODO - investigate why IE6/7 seem to fire recursive resize in e.getXY
54866                 // breaking QuickTip#onTargetOver (EXTJSIV-1608)
54867                 freezeEvent: true,
54868
54869                 mouseover: me.onTargetOver,
54870                 mouseout: me.onTargetOut,
54871                 mousemove: me.onMouseMove,
54872                 scope: me
54873             });
54874         }
54875         if (me.anchor) {
54876             me.anchorTarget = me.target;
54877         }
54878     },
54879
54880     // private
54881     onMouseMove: function(e) {
54882         var me = this,
54883             t = me.delegate ? e.getTarget(me.delegate) : me.triggerElement = true,
54884             xy;
54885         if (t) {
54886             me.targetXY = e.getXY();
54887             if (t === me.triggerElement) {
54888                 if (!me.hidden && me.trackMouse) {
54889                     xy = me.getTargetXY();
54890                     if (me.constrainPosition) {
54891                         xy = me.el.adjustForConstraints(xy, me.el.dom.parentNode);
54892                     }
54893                     me.setPagePosition(xy);
54894                 }
54895             } else {
54896                 me.hide();
54897                 me.lastActive = new Date(0);
54898                 me.onTargetOver(e);
54899             }
54900         } else if ((!me.closable && me.isVisible()) && me.autoHide !== false) {
54901             me.hide();
54902         }
54903     },
54904
54905     // private
54906     getTargetXY: function() {
54907         var me = this,
54908             mouseOffset;
54909         if (me.delegate) {
54910             me.anchorTarget = me.triggerElement;
54911         }
54912         if (me.anchor) {
54913             me.targetCounter++;
54914                 var offsets = me.getOffsets(),
54915                     xy = (me.anchorToTarget && !me.trackMouse) ? me.el.getAlignToXY(me.anchorTarget, me.getAnchorAlign()) : me.targetXY,
54916                     dw = Ext.core.Element.getViewWidth() - 5,
54917                     dh = Ext.core.Element.getViewHeight() - 5,
54918                     de = document.documentElement,
54919                     bd = document.body,
54920                     scrollX = (de.scrollLeft || bd.scrollLeft || 0) + 5,
54921                     scrollY = (de.scrollTop || bd.scrollTop || 0) + 5,
54922                     axy = [xy[0] + offsets[0], xy[1] + offsets[1]],
54923                     sz = me.getSize(),
54924                     constrainPosition = me.constrainPosition;
54925
54926             me.anchorEl.removeCls(me.anchorCls);
54927
54928             if (me.targetCounter < 2 && constrainPosition) {
54929                 if (axy[0] < scrollX) {
54930                     if (me.anchorToTarget) {
54931                         me.defaultAlign = 'l-r';
54932                         if (me.mouseOffset) {
54933                             me.mouseOffset[0] *= -1;
54934                         }
54935                     }
54936                     me.anchor = 'left';
54937                     return me.getTargetXY();
54938                 }
54939                 if (axy[0] + sz.width > dw) {
54940                     if (me.anchorToTarget) {
54941                         me.defaultAlign = 'r-l';
54942                         if (me.mouseOffset) {
54943                             me.mouseOffset[0] *= -1;
54944                         }
54945                     }
54946                     me.anchor = 'right';
54947                     return me.getTargetXY();
54948                 }
54949                 if (axy[1] < scrollY) {
54950                     if (me.anchorToTarget) {
54951                         me.defaultAlign = 't-b';
54952                         if (me.mouseOffset) {
54953                             me.mouseOffset[1] *= -1;
54954                         }
54955                     }
54956                     me.anchor = 'top';
54957                     return me.getTargetXY();
54958                 }
54959                 if (axy[1] + sz.height > dh) {
54960                     if (me.anchorToTarget) {
54961                         me.defaultAlign = 'b-t';
54962                         if (me.mouseOffset) {
54963                             me.mouseOffset[1] *= -1;
54964                         }
54965                     }
54966                     me.anchor = 'bottom';
54967                     return me.getTargetXY();
54968                 }
54969             }
54970
54971             me.anchorCls = Ext.baseCSSPrefix + 'tip-anchor-' + me.getAnchorPosition();
54972             me.anchorEl.addCls(me.anchorCls);
54973             me.targetCounter = 0;
54974             return axy;
54975         } else {
54976             mouseOffset = me.getMouseOffset();
54977             return (me.targetXY) ? [me.targetXY[0] + mouseOffset[0], me.targetXY[1] + mouseOffset[1]] : mouseOffset;
54978         }
54979     },
54980
54981     getMouseOffset: function() {
54982         var me = this,
54983         offset = me.anchor ? [0, 0] : [15, 18];
54984         if (me.mouseOffset) {
54985             offset[0] += me.mouseOffset[0];
54986             offset[1] += me.mouseOffset[1];
54987         }
54988         return offset;
54989     },
54990
54991     // private
54992     getAnchorPosition: function() {
54993         var me = this,
54994             m;
54995         if (me.anchor) {
54996             me.tipAnchor = me.anchor.charAt(0);
54997         } else {
54998             m = me.defaultAlign.match(/^([a-z]+)-([a-z]+)(\?)?$/);
54999             if (!m) {
55000                 Ext.Error.raise('The AnchorTip.defaultAlign value "' + me.defaultAlign + '" is invalid.');
55001             }
55002             me.tipAnchor = m[1].charAt(0);
55003         }
55004
55005         switch (me.tipAnchor) {
55006         case 't':
55007             return 'top';
55008         case 'b':
55009             return 'bottom';
55010         case 'r':
55011             return 'right';
55012         }
55013         return 'left';
55014     },
55015
55016     // private
55017     getAnchorAlign: function() {
55018         switch (this.anchor) {
55019         case 'top':
55020             return 'tl-bl';
55021         case 'left':
55022             return 'tl-tr';
55023         case 'right':
55024             return 'tr-tl';
55025         default:
55026             return 'bl-tl';
55027         }
55028     },
55029
55030     // private
55031     getOffsets: function() {
55032         var me = this,
55033             mouseOffset,
55034             offsets,
55035             ap = me.getAnchorPosition().charAt(0);
55036         if (me.anchorToTarget && !me.trackMouse) {
55037             switch (ap) {
55038             case 't':
55039                 offsets = [0, 9];
55040                 break;
55041             case 'b':
55042                 offsets = [0, -13];
55043                 break;
55044             case 'r':
55045                 offsets = [ - 13, 0];
55046                 break;
55047             default:
55048                 offsets = [9, 0];
55049                 break;
55050             }
55051         } else {
55052             switch (ap) {
55053             case 't':
55054                 offsets = [ - 15 - me.anchorOffset, 30];
55055                 break;
55056             case 'b':
55057                 offsets = [ - 19 - me.anchorOffset, -13 - me.el.dom.offsetHeight];
55058                 break;
55059             case 'r':
55060                 offsets = [ - 15 - me.el.dom.offsetWidth, -13 - me.anchorOffset];
55061                 break;
55062             default:
55063                 offsets = [25, -13 - me.anchorOffset];
55064                 break;
55065             }
55066         }
55067         mouseOffset = me.getMouseOffset();
55068         offsets[0] += mouseOffset[0];
55069         offsets[1] += mouseOffset[1];
55070
55071         return offsets;
55072     },
55073
55074     // private
55075     onTargetOver: function(e) {
55076         var me = this,
55077             t;
55078
55079         if (me.disabled || e.within(me.target.dom, true)) {
55080             return;
55081         }
55082         t = e.getTarget(me.delegate);
55083         if (t) {
55084             me.triggerElement = t;
55085             me.clearTimer('hide');
55086             me.targetXY = e.getXY();
55087             me.delayShow();
55088         }
55089     },
55090
55091     // private
55092     delayShow: function() {
55093         var me = this;
55094         if (me.hidden && !me.showTimer) {
55095             if (Ext.Date.getElapsed(me.lastActive) < me.quickShowInterval) {
55096                 me.show();
55097             } else {
55098                 me.showTimer = Ext.defer(me.show, me.showDelay, me);
55099             }
55100         }
55101         else if (!me.hidden && me.autoHide !== false) {
55102             me.show();
55103         }
55104     },
55105
55106     // private
55107     onTargetOut: function(e) {
55108         var me = this;
55109         if (me.disabled || e.within(me.target.dom, true)) {
55110             return;
55111         }
55112         me.clearTimer('show');
55113         if (me.autoHide !== false) {
55114             me.delayHide();
55115         }
55116     },
55117
55118     // private
55119     delayHide: function() {
55120         var me = this;
55121         if (!me.hidden && !me.hideTimer) {
55122             me.hideTimer = Ext.defer(me.hide, me.hideDelay, me);
55123         }
55124     },
55125
55126     /**
55127      * Hides this tooltip if visible.
55128      */
55129     hide: function() {
55130         var me = this;
55131         me.clearTimer('dismiss');
55132         me.lastActive = new Date();
55133         if (me.anchorEl) {
55134             me.anchorEl.hide();
55135         }
55136         me.callParent(arguments);
55137         delete me.triggerElement;
55138     },
55139
55140     /**
55141      * Shows this tooltip at the current event target XY position.
55142      */
55143     show: function() {
55144         var me = this;
55145
55146         // Show this Component first, so that sizing can be calculated
55147         // pre-show it off screen so that the el will have dimensions
55148         this.callParent();
55149         if (this.hidden === false) {
55150             me.setPagePosition(-10000, -10000);
55151
55152             if (me.anchor) {
55153                 me.anchor = me.origAnchor;
55154             }
55155             me.showAt(me.getTargetXY());
55156
55157             if (me.anchor) {
55158                 me.syncAnchor();
55159                 me.anchorEl.show();
55160             } else {
55161                 me.anchorEl.hide();
55162             }
55163         }
55164     },
55165
55166     // inherit docs
55167     showAt: function(xy) {
55168         var me = this;
55169         me.lastActive = new Date();
55170         me.clearTimers();
55171
55172         // Only call if this is hidden. May have been called from show above.
55173         if (!me.isVisible()) {
55174             this.callParent(arguments);
55175         }
55176
55177         // Show may have been vetoed.
55178         if (me.isVisible()) {
55179             me.setPagePosition(xy[0], xy[1]);
55180             if (me.constrainPosition || me.constrain) {
55181                 me.doConstrain();
55182             }
55183             me.toFront(true);
55184         }
55185
55186         if (me.dismissDelay && me.autoHide !== false) {
55187             me.dismissTimer = Ext.defer(me.hide, me.dismissDelay, me);
55188         }
55189         if (me.anchor) {
55190             me.syncAnchor();
55191             if (!me.anchorEl.isVisible()) {
55192                 me.anchorEl.show();
55193             }
55194         } else {
55195             me.anchorEl.hide();
55196         }
55197     },
55198
55199     // private
55200     syncAnchor: function() {
55201         var me = this,
55202             anchorPos,
55203             targetPos,
55204             offset;
55205         switch (me.tipAnchor.charAt(0)) {
55206         case 't':
55207             anchorPos = 'b';
55208             targetPos = 'tl';
55209             offset = [20 + me.anchorOffset, 1];
55210             break;
55211         case 'r':
55212             anchorPos = 'l';
55213             targetPos = 'tr';
55214             offset = [ - 1, 12 + me.anchorOffset];
55215             break;
55216         case 'b':
55217             anchorPos = 't';
55218             targetPos = 'bl';
55219             offset = [20 + me.anchorOffset, -1];
55220             break;
55221         default:
55222             anchorPos = 'r';
55223             targetPos = 'tl';
55224             offset = [1, 12 + me.anchorOffset];
55225             break;
55226         }
55227         me.anchorEl.alignTo(me.el, anchorPos + '-' + targetPos, offset);
55228     },
55229
55230     // private
55231     setPagePosition: function(x, y) {
55232         var me = this;
55233         me.callParent(arguments);
55234         if (me.anchor) {
55235             me.syncAnchor();
55236         }
55237     },
55238
55239     // private
55240     clearTimer: function(name) {
55241         name = name + 'Timer';
55242         clearTimeout(this[name]);
55243         delete this[name];
55244     },
55245
55246     // private
55247     clearTimers: function() {
55248         var me = this;
55249         me.clearTimer('show');
55250         me.clearTimer('dismiss');
55251         me.clearTimer('hide');
55252     },
55253
55254     // private
55255     onShow: function() {
55256         var me = this;
55257         me.callParent();
55258         me.mon(Ext.getDoc(), 'mousedown', me.onDocMouseDown, me);
55259     },
55260
55261     // private
55262     onHide: function() {
55263         var me = this;
55264         me.callParent();
55265         me.mun(Ext.getDoc(), 'mousedown', me.onDocMouseDown, me);
55266     },
55267
55268     // private
55269     onDocMouseDown: function(e) {
55270         var me = this;
55271         if (me.autoHide !== true && !me.closable && !e.within(me.el.dom)) {
55272             me.disable();
55273             Ext.defer(me.doEnable, 100, me);
55274         }
55275     },
55276
55277     // private
55278     doEnable: function() {
55279         if (!this.isDestroyed) {
55280             this.enable();
55281         }
55282     },
55283
55284     // private
55285     onDisable: function() {
55286         this.callParent();
55287         this.clearTimers();
55288         this.hide();
55289     },
55290
55291     beforeDestroy: function() {
55292         var me = this;
55293         me.clearTimers();
55294         Ext.destroy(me.anchorEl);
55295         delete me.anchorEl;
55296         delete me.target;
55297         delete me.anchorTarget;
55298         delete me.triggerElement;
55299         me.callParent();
55300     },
55301
55302     // private
55303     onDestroy: function() {
55304         Ext.getDoc().un('mousedown', this.onDocMouseDown, this);
55305         this.callParent();
55306     }
55307 });
55308
55309 /**
55310  * @class Ext.tip.QuickTip
55311  * @extends Ext.tip.ToolTip
55312  * A specialized tooltip class for tooltips that can be specified in markup and automatically managed by the global
55313  * {@link Ext.tip.QuickTipManager} instance.  See the QuickTipManager class header for additional usage details and examples.
55314  * @constructor
55315  * Create a new Tip
55316  * @param {Object} config The configuration options
55317  * @xtype quicktip
55318  */
55319 Ext.define('Ext.tip.QuickTip', {
55320     extend: 'Ext.tip.ToolTip',
55321     alternateClassName: 'Ext.QuickTip',
55322     /**
55323      * @cfg {Mixed} target The target HTMLElement, Ext.core.Element or id to associate with this Quicktip (defaults to the document).
55324      */
55325     /**
55326      * @cfg {Boolean} interceptTitles True to automatically use the element's DOM title value if available (defaults to false).
55327      */
55328     interceptTitles : false,
55329
55330     // Force creation of header Component
55331     title: '&#160;',
55332
55333     // private
55334     tagConfig : {
55335         namespace : "data-",
55336         attribute : "qtip",
55337         width : "qwidth",
55338         target : "target",
55339         title : "qtitle",
55340         hide : "hide",
55341         cls : "qclass",
55342         align : "qalign",
55343         anchor : "anchor"
55344     },
55345
55346     // private
55347     initComponent : function(){
55348         var me = this;
55349         
55350         me.target = me.target || Ext.getDoc();
55351         me.targets = me.targets || {};
55352         me.callParent();
55353     },
55354
55355     /**
55356      * Configures a new quick tip instance and assigns it to a target element.  The following config values are
55357      * supported (for example usage, see the {@link Ext.tip.QuickTipManager} class header):
55358      * <div class="mdetail-params"><ul>
55359      * <li>autoHide</li>
55360      * <li>cls</li>
55361      * <li>dismissDelay (overrides the singleton value)</li>
55362      * <li>target (required)</li>
55363      * <li>text (required)</li>
55364      * <li>title</li>
55365      * <li>width</li></ul></div>
55366      * @param {Object} config The config object
55367      */
55368     register : function(config){
55369         var configs = Ext.isArray(config) ? config : arguments,
55370             i = 0,
55371             len = configs.length,
55372             target, j, targetLen;
55373             
55374         for (; i < len; i++) {
55375             config = configs[i];
55376             target = config.target;
55377             if (target) {
55378                 if (Ext.isArray(target)) {
55379                     for (j = 0, targetLen = target.length; j < targetLen; j++) {
55380                         this.targets[Ext.id(target[j])] = config;
55381                     }
55382                 } else{
55383                     this.targets[Ext.id(target)] = config;
55384                 }
55385             }
55386         }
55387     },
55388
55389     /**
55390      * Removes this quick tip from its element and destroys it.
55391      * @param {String/HTMLElement/Element} el The element from which the quick tip is to be removed.
55392      */
55393     unregister : function(el){
55394         delete this.targets[Ext.id(el)];
55395     },
55396     
55397     /**
55398      * Hides a visible tip or cancels an impending show for a particular element.
55399      * @param {String/HTMLElement/Element} el The element that is the target of the tip.
55400      */
55401     cancelShow: function(el){
55402         var me = this,
55403             activeTarget = me.activeTarget;
55404             
55405         el = Ext.get(el).dom;
55406         if (me.isVisible()) {
55407             if (activeTarget && activeTarget.el == el) {
55408                 me.hide();
55409             }
55410         } else if (activeTarget && activeTarget.el == el) {
55411             me.clearTimer('show');
55412         }
55413     },
55414     
55415     getTipCfg: function(e) {
55416         var t = e.getTarget(),
55417             ttp, 
55418             cfg;
55419         
55420         if(this.interceptTitles && t.title && Ext.isString(t.title)){
55421             ttp = t.title;
55422             t.qtip = ttp;
55423             t.removeAttribute("title");
55424             e.preventDefault();
55425         } 
55426         else {            
55427             cfg = this.tagConfig;
55428             t = e.getTarget('[' + cfg.namespace + cfg.attribute + ']');
55429             if (t) {
55430                 ttp = t.getAttribute(cfg.namespace + cfg.attribute);
55431             }
55432         }
55433         return ttp;
55434     },
55435
55436     // private
55437     onTargetOver : function(e){
55438         var me = this,
55439             target = e.getTarget(),
55440             elTarget,
55441             cfg,
55442             ns,
55443             ttp,
55444             autoHide;
55445         
55446         if (me.disabled) {
55447             return;
55448         }
55449
55450         // TODO - this causes "e" to be recycled in IE6/7 (EXTJSIV-1608) so ToolTip#setTarget
55451         // was changed to include freezeEvent. The issue seems to be a nested 'resize' event
55452         // that smashed Ext.EventObject.
55453         me.targetXY = e.getXY();
55454
55455         if(!target || target.nodeType !== 1 || target == document || target == document.body){
55456             return;
55457         }
55458         
55459         if (me.activeTarget && ((target == me.activeTarget.el) || Ext.fly(me.activeTarget.el).contains(target))) {
55460             me.clearTimer('hide');
55461             me.show();
55462             return;
55463         }
55464         
55465         if (target) {
55466             Ext.Object.each(me.targets, function(key, value) {
55467                 var targetEl = Ext.fly(value.target);
55468                 if (targetEl && (targetEl.dom === target || targetEl.contains(target))) {
55469                     elTarget = targetEl.dom;
55470                     return false;
55471                 }
55472             });
55473             if (elTarget) {
55474                 me.activeTarget = me.targets[elTarget.id];
55475                 me.activeTarget.el = target;
55476                 me.anchor = me.activeTarget.anchor;
55477                 if (me.anchor) {
55478                     me.anchorTarget = target;
55479                 }
55480                 me.delayShow();
55481                 return;
55482             }
55483         }
55484
55485         elTarget = Ext.get(target);
55486         cfg = me.tagConfig;
55487         ns = cfg.namespace; 
55488         ttp = me.getTipCfg(e);
55489         
55490         if (ttp) {
55491             autoHide = elTarget.getAttribute(ns + cfg.hide);
55492                  
55493             me.activeTarget = {
55494                 el: target,
55495                 text: ttp,
55496                 width: +elTarget.getAttribute(ns + cfg.width) || null,
55497                 autoHide: autoHide != "user" && autoHide !== 'false',
55498                 title: elTarget.getAttribute(ns + cfg.title),
55499                 cls: elTarget.getAttribute(ns + cfg.cls),
55500                 align: elTarget.getAttribute(ns + cfg.align)
55501                 
55502             };
55503             me.anchor = elTarget.getAttribute(ns + cfg.anchor);
55504             if (me.anchor) {
55505                 me.anchorTarget = target;
55506             }
55507             me.delayShow();
55508         }
55509     },
55510
55511     // private
55512     onTargetOut : function(e){
55513         var me = this;
55514         
55515         // If moving within the current target, and it does not have a new tip, ignore the mouseout
55516         if (me.activeTarget && e.within(me.activeTarget.el) && !me.getTipCfg(e)) {
55517             return;
55518         }
55519
55520         me.clearTimer('show');
55521         if (me.autoHide !== false) {
55522             me.delayHide();
55523         }
55524     },
55525
55526     // inherit docs
55527     showAt : function(xy){
55528         var me = this,
55529             target = me.activeTarget;
55530         
55531         if (target) {
55532             if (!me.rendered) {
55533                 me.render(Ext.getBody());
55534                 me.activeTarget = target;
55535             }
55536             if (target.title) {
55537                 me.setTitle(target.title || '');
55538                 me.header.show();
55539             } else {
55540                 me.header.hide();
55541             }
55542             me.body.update(target.text);
55543             me.autoHide = target.autoHide;
55544             me.dismissDelay = target.dismissDelay || me.dismissDelay;
55545             if (me.lastCls) {
55546                 me.el.removeCls(me.lastCls);
55547                 delete me.lastCls;
55548             }
55549             if (target.cls) {
55550                 me.el.addCls(target.cls);
55551                 me.lastCls = target.cls;
55552             }
55553
55554             me.setWidth(target.width);
55555             
55556             if (me.anchor) {
55557                 me.constrainPosition = false;
55558             } else if (target.align) { // TODO: this doesn't seem to work consistently
55559                 xy = me.el.getAlignToXY(target.el, target.align);
55560                 me.constrainPosition = false;
55561             }else{
55562                 me.constrainPosition = true;
55563             }
55564         }
55565         me.callParent([xy]);
55566     },
55567
55568     // inherit docs
55569     hide: function(){
55570         delete this.activeTarget;
55571         this.callParent();
55572     }
55573 });
55574
55575 /**
55576  * @class Ext.tip.QuickTipManager
55577  * <p>Provides attractive and customizable tooltips for any element. The QuickTips
55578  * singleton is used to configure and manage tooltips globally for multiple elements
55579  * in a generic manner.  To create individual tooltips with maximum customizability,
55580  * you should consider either {@link Ext.tip.Tip} or {@link Ext.tip.ToolTip}.</p>
55581  * <p>Quicktips can be configured via tag attributes directly in markup, or by
55582  * registering quick tips programmatically via the {@link #register} method.</p>
55583  * <p>The singleton's instance of {@link Ext.tip.QuickTip} is available via
55584  * {@link #getQuickTip}, and supports all the methods, and all the all the
55585  * configuration properties of Ext.tip.QuickTip. These settings will apply to all
55586  * tooltips shown by the singleton.</p>
55587  * <p>Below is the summary of the configuration properties which can be used.
55588  * For detailed descriptions see the config options for the {@link Ext.tip.QuickTip QuickTip} class</p>
55589  * <p><b>QuickTips singleton configs (all are optional)</b></p>
55590  * <div class="mdetail-params"><ul><li>dismissDelay</li>
55591  * <li>hideDelay</li>
55592  * <li>maxWidth</li>
55593  * <li>minWidth</li>
55594  * <li>showDelay</li>
55595  * <li>trackMouse</li></ul></div>
55596  * <p><b>Target element configs (optional unless otherwise noted)</b></p>
55597  * <div class="mdetail-params"><ul><li>autoHide</li>
55598  * <li>cls</li>
55599  * <li>dismissDelay (overrides singleton value)</li>
55600  * <li>target (required)</li>
55601  * <li>text (required)</li>
55602  * <li>title</li>
55603  * <li>width</li></ul></div>
55604  * <p>Here is an example showing how some of these config options could be used:</p>
55605  *
55606  * {@img Ext.tip.QuickTipManager/Ext.tip.QuickTipManager.png Ext.tip.QuickTipManager component}
55607  *
55608  * ## Code
55609  *    // Init the singleton.  Any tag-based quick tips will start working.
55610  *    Ext.tip.QuickTipManager.init();
55611  *    
55612  *    // Apply a set of config properties to the singleton
55613  *    Ext.apply(Ext.tip.QuickTipManager.getQuickTip(), {
55614  *        maxWidth: 200,
55615  *        minWidth: 100,
55616  *        showDelay: 50      // Show 50ms after entering target
55617  *    });
55618  *    
55619  *    // Create a small panel to add a quick tip to
55620  *    Ext.create('Ext.container.Container', {
55621  *        id: 'quickTipContainer',
55622  *        width: 200,
55623  *        height: 150,
55624  *        style: {
55625  *            backgroundColor:'#000000'
55626  *        },
55627  *        renderTo: Ext.getBody()
55628  *    });
55629  *
55630  *    
55631  *    // Manually register a quick tip for a specific element
55632  *    Ext.tip.QuickTipManager.register({
55633  *        target: 'quickTipContainer',
55634  *        title: 'My Tooltip',
55635  *        text: 'This tooltip was added in code',
55636  *        width: 100,
55637  *        dismissDelay: 10000 // Hide after 10 seconds hover
55638  *    });
55639 </code></pre>
55640  * <p>To register a quick tip in markup, you simply add one or more of the valid QuickTip attributes prefixed with
55641  * the <b>ext:</b> namespace.  The HTML element itself is automatically set as the quick tip target. Here is the summary
55642  * of supported attributes (optional unless otherwise noted):</p>
55643  * <ul><li><b>hide</b>: Specifying "user" is equivalent to setting autoHide = false.  Any other value will be the
55644  * same as autoHide = true.</li>
55645  * <li><b>qclass</b>: A CSS class to be applied to the quick tip (equivalent to the 'cls' target element config).</li>
55646  * <li><b>qtip (required)</b>: The quick tip text (equivalent to the 'text' target element config).</li>
55647  * <li><b>qtitle</b>: The quick tip title (equivalent to the 'title' target element config).</li>
55648  * <li><b>qwidth</b>: The quick tip width (equivalent to the 'width' target element config).</li></ul>
55649  * <p>Here is an example of configuring an HTML element to display a tooltip from markup:</p>
55650  * <pre><code>
55651 // Add a quick tip to an HTML button
55652 &lt;input type="button" value="OK" ext:qtitle="OK Button" ext:qwidth="100"
55653      data-qtip="This is a quick tip from markup!">&lt;/input>
55654 </code></pre>
55655  * @singleton
55656  */
55657 Ext.define('Ext.tip.QuickTipManager', function() {
55658     var tip,
55659         disabled = false;
55660
55661     return {
55662         requires: ['Ext.tip.QuickTip'],
55663         singleton: true,
55664         alternateClassName: 'Ext.QuickTips',
55665         /**
55666          * Initialize the global QuickTips instance and prepare any quick tips.
55667          * @param {Boolean} autoRender True to render the QuickTips container immediately to preload images. (Defaults to true) 
55668          */
55669         init : function(autoRender){
55670             if (!tip) {
55671                 if (!Ext.isReady) {
55672                     Ext.onReady(function(){
55673                         Ext.tip.QuickTipManager.init(autoRender);
55674                     });
55675                     return;
55676                 }
55677                 tip = Ext.create('Ext.tip.QuickTip', {
55678                     disabled: disabled,
55679                     renderTo: autoRender !== false ? document.body : undefined
55680                 });
55681             }
55682         },
55683
55684         /**
55685          * Destroy the QuickTips instance.
55686          */
55687         destroy: function() {
55688             if (tip) {
55689                 var undef;
55690                 tip.destroy();
55691                 tip = undef;
55692             }
55693         },
55694
55695         // Protected method called by the dd classes
55696         ddDisable : function(){
55697             // don't disable it if we don't need to
55698             if(tip && !disabled){
55699                 tip.disable();
55700             }
55701         },
55702
55703         // Protected method called by the dd classes
55704         ddEnable : function(){
55705             // only enable it if it hasn't been disabled
55706             if(tip && !disabled){
55707                 tip.enable();
55708             }
55709         },
55710
55711         /**
55712          * Enable quick tips globally.
55713          */
55714         enable : function(){
55715             if(tip){
55716                 tip.enable();
55717             }
55718             disabled = false;
55719         },
55720
55721         /**
55722          * Disable quick tips globally.
55723          */
55724         disable : function(){
55725             if(tip){
55726                 tip.disable();
55727             }
55728             disabled = true;
55729         },
55730
55731         /**
55732          * Returns true if quick tips are enabled, else false.
55733          * @return {Boolean}
55734          */
55735         isEnabled : function(){
55736             return tip !== undefined && !tip.disabled;
55737         },
55738
55739         /**
55740          * Gets the single {@link Ext.tip.QuickTip QuickTip} instance used to show tips from all registered elements.
55741          * @return {Ext.tip.QuickTip}
55742          */
55743         getQuickTip : function(){
55744             return tip;
55745         },
55746
55747         /**
55748          * Configures a new quick tip instance and assigns it to a target element.  See
55749          * {@link Ext.tip.QuickTip#register} for details.
55750          * @param {Object} config The config object
55751          */
55752         register : function(){
55753             tip.register.apply(tip, arguments);
55754         },
55755
55756         /**
55757          * Removes any registered quick tip from the target element and destroys it.
55758          * @param {String/HTMLElement/Element} el The element from which the quick tip is to be removed.
55759          */
55760         unregister : function(){
55761             tip.unregister.apply(tip, arguments);
55762         },
55763
55764         /**
55765          * Alias of {@link #register}.
55766          * @param {Object} config The config object
55767          */
55768         tips : function(){
55769             tip.register.apply(tip, arguments);
55770         }
55771     };
55772 }());
55773 /**
55774  * @class Ext.app.Application
55775  * @constructor
55776  * 
55777  * Represents an Ext JS 4 application, which is typically a single page app using a {@link Ext.container.Viewport Viewport}.
55778  * A typical Ext.app.Application might look like this:
55779  * 
55780  * Ext.application({
55781      name: 'MyApp',
55782      launch: function() {
55783          Ext.create('Ext.container.Viewport', {
55784              items: {
55785                  html: 'My App'
55786              }
55787          });
55788      }
55789  });
55790  * 
55791  * This does several things. First it creates a global variable called 'MyApp' - all of your Application's classes (such
55792  * as its Models, Views and Controllers) will reside under this single namespace, which drastically lowers the chances
55793  * of colliding global variables.
55794  * 
55795  * When the page is ready and all of your JavaScript has loaded, your Application's {@link #launch} function is called,
55796  * at which time you can run the code that starts your app. Usually this consists of creating a Viewport, as we do in
55797  * the example above.
55798  * 
55799  * <u>Telling Application about the rest of the app</u>
55800  * 
55801  * Because an Ext.app.Application represents an entire app, we should tell it about the other parts of the app - namely
55802  * the Models, Views and Controllers that are bundled with the application. Let's say we have a blog management app; we
55803  * might have Models and Controllers for Posts and Comments, and Views for listing, adding and editing Posts and Comments.
55804  * Here's how we'd tell our Application about all these things:
55805  * 
55806  * Ext.application({
55807      name: 'Blog',
55808      models: ['Post', 'Comment'],
55809      controllers: ['Posts', 'Comments'],
55810
55811      launch: function() {
55812          ...
55813      }
55814  });
55815  * 
55816  * Note that we didn't actually list the Views directly in the Application itself. This is because Views are managed by
55817  * Controllers, so it makes sense to keep those dependencies there. The Application will load each of the specified 
55818  * Controllers using the pathing conventions laid out in the <a href="../guide/application_architecture">application 
55819  * architecture guide</a> - in this case expecting the controllers to reside in app/controller/Posts.js and
55820  * app/controller/Comments.js. In turn, each Controller simply needs to list the Views it uses and they will be
55821  * automatically loaded. Here's how our Posts controller like be defined:
55822  * 
55823  * Ext.define('MyApp.controller.Posts', {
55824      extend: 'Ext.app.Controller',
55825      views: ['posts.List', 'posts.Edit'],
55826
55827      //the rest of the Controller here
55828  });
55829  * 
55830  * Because we told our Application about our Models and Controllers, and our Controllers about their Views, Ext JS will
55831  * automatically load all of our app files for us. This means we don't have to manually add script tags into our html
55832  * files whenever we add a new class, but more importantly it enables us to create a minimized build of our entire 
55833  * application using the Ext JS 4 SDK Tools.
55834  * 
55835  * For more information about writing Ext JS 4 applications, please see the <a href="../guide/application_architecture">
55836  * application architecture guide</a>.
55837  * 
55838  * @markdown
55839  * @docauthor Ed Spencer
55840  */
55841 Ext.define('Ext.app.Application', {
55842     extend: 'Ext.app.Controller',
55843
55844     requires: [
55845         'Ext.ModelManager',
55846         'Ext.data.Model',
55847         'Ext.data.StoreManager',
55848         'Ext.tip.QuickTipManager',
55849         'Ext.ComponentManager',
55850         'Ext.app.EventBus'
55851     ],
55852
55853     /**
55854      * @cfg {Object} name The name of your application. This will also be the namespace for your views, controllers
55855      * models and stores. Don't use spaces or special characters in the name.
55856      */
55857
55858     /**
55859      * @cfg {Object} scope The scope to execute the {@link #launch} function in. Defaults to the Application
55860      * instance.
55861      */
55862     scope: undefined,
55863
55864     /**
55865      * @cfg {Boolean} enableQuickTips True to automatically set up Ext.tip.QuickTip support (defaults to true)
55866      */
55867     enableQuickTips: true,
55868
55869     /**
55870      * @cfg {String} defaultUrl When the app is first loaded, this url will be redirected to. Defaults to undefined
55871      */
55872
55873     /**
55874      * @cfg {String} appFolder The path to the directory which contains all application's classes.
55875      * This path will be registered via {@link Ext.Loader#setPath} for the namespace specified in the {@link #name name} config.
55876      * Defaults to 'app'
55877      */
55878     appFolder: 'app',
55879
55880     /**
55881      * @cfg {Boolean} autoCreateViewport Automatically loads and instantiates AppName.view.Viewport before firing the launch function.
55882      */
55883     autoCreateViewport: true,
55884
55885     constructor: function(config) {
55886         config = config || {};
55887         Ext.apply(this, config);
55888
55889         var requires = config.requires || [];
55890
55891         Ext.Loader.setPath(this.name, this.appFolder);
55892
55893         if (this.paths) {
55894             Ext.Object.each(this.paths, function(key, value) {
55895                 Ext.Loader.setPath(key, value);
55896             });
55897         }
55898
55899         this.callParent(arguments);
55900
55901         this.eventbus = Ext.create('Ext.app.EventBus');
55902
55903         var controllers = this.controllers,
55904             ln = controllers.length,
55905             i, controller;
55906
55907         this.controllers = Ext.create('Ext.util.MixedCollection');
55908
55909         if (this.autoCreateViewport) {
55910             requires.push(this.getModuleClassName('Viewport', 'view'));
55911         }
55912
55913         for (i = 0; i < ln; i++) {
55914             requires.push(this.getModuleClassName(controllers[i], 'controller'));
55915         }
55916
55917         Ext.require(requires);
55918
55919         Ext.onReady(function() {
55920             for (i = 0; i < ln; i++) {
55921                 controller = this.getController(controllers[i]);
55922                 controller.init(this);
55923             }
55924
55925             this.onBeforeLaunch.call(this);
55926         }, this);
55927     },
55928
55929     control: function(selectors, listeners, controller) {
55930         this.eventbus.control(selectors, listeners, controller);
55931     },
55932
55933     /**
55934      * Called automatically when the page has completely loaded. This is an empty function that should be
55935      * overridden by each application that needs to take action on page load
55936      * @property launch
55937      * @type Function
55938      * @param {String} profile The detected {@link #profiles application profile}
55939      * @return {Boolean} By default, the Application will dispatch to the configured startup controller and
55940      * action immediately after running the launch function. Return false to prevent this behavior.
55941      */
55942     launch: Ext.emptyFn,
55943
55944     /**
55945      * @private
55946      */
55947     onBeforeLaunch: function() {
55948         if (this.enableQuickTips) {
55949             Ext.tip.QuickTipManager.init();
55950         }
55951
55952         if (this.autoCreateViewport) {
55953             this.getView('Viewport').create();
55954         }
55955
55956         this.launch.call(this.scope || this);
55957         this.launched = true;
55958         this.fireEvent('launch', this);
55959
55960         this.controllers.each(function(controller) {
55961             controller.onLaunch(this);
55962         }, this);
55963     },
55964
55965     getModuleClassName: function(name, type) {
55966         var namespace = Ext.Loader.getPrefix(name);
55967
55968         if (namespace.length > 0 && namespace !== name) {
55969             return name;
55970         }
55971
55972         return this.name + '.' + type + '.' + name;
55973     },
55974
55975     getController: function(name) {
55976         var controller = this.controllers.get(name);
55977
55978         if (!controller) {
55979             controller = Ext.create(this.getModuleClassName(name, 'controller'), {
55980                 application: this,
55981                 id: name
55982             });
55983
55984             this.controllers.add(controller);
55985         }
55986
55987         return controller;
55988     },
55989
55990     getStore: function(name) {
55991         var store = Ext.StoreManager.get(name);
55992
55993         if (!store) {
55994             store = Ext.create(this.getModuleClassName(name, 'store'), {
55995                 storeId: name
55996             });
55997         }
55998
55999         return store;
56000     },
56001
56002     getModel: function(model) {
56003         model = this.getModuleClassName(model, 'model');
56004
56005         return Ext.ModelManager.getModel(model);
56006     },
56007
56008     getView: function(view) {
56009         view = this.getModuleClassName(view, 'view');
56010
56011         return Ext.ClassManager.get(view);
56012     }
56013 });
56014
56015 /**
56016  * @class Ext.chart.Callout
56017  * @ignore
56018  */
56019 Ext.define('Ext.chart.Callout', {
56020
56021     /* Begin Definitions */
56022
56023     /* End Definitions */
56024
56025     constructor: function(config) {
56026         if (config.callouts) {
56027             config.callouts.styles = Ext.applyIf(config.callouts.styles || {}, {
56028                 color: "#000",
56029                 font: "11px Helvetica, sans-serif"
56030             });
56031             this.callouts = Ext.apply(this.callouts || {}, config.callouts);
56032             this.calloutsArray = [];
56033         }
56034     },
56035
56036     renderCallouts: function() {
56037         if (!this.callouts) {
56038             return;
56039         }
56040
56041         var me = this,
56042             items = me.items,
56043             animate = me.chart.animate,
56044             config = me.callouts,
56045             styles = config.styles,
56046             group = me.calloutsArray,
56047             store = me.chart.store,
56048             len = store.getCount(),
56049             ratio = items.length / len,
56050             previouslyPlacedCallouts = [],
56051             i,
56052             count,
56053             j,
56054             p;
56055             
56056         for (i = 0, count = 0; i < len; i++) {
56057             for (j = 0; j < ratio; j++) {
56058                 var item = items[count],
56059                     label = group[count],
56060                     storeItem = store.getAt(i),
56061                     display;
56062                 
56063                 display = config.filter(storeItem);
56064                 
56065                 if (!display && !label) {
56066                     count++;
56067                     continue;               
56068                 }
56069                 
56070                 if (!label) {
56071                     group[count] = label = me.onCreateCallout(storeItem, item, i, display, j, count);
56072                 }
56073                 for (p in label) {
56074                     if (label[p] && label[p].setAttributes) {
56075                         label[p].setAttributes(styles, true);
56076                     }
56077                 }
56078                 if (!display) {
56079                     for (p in label) {
56080                         if (label[p]) {
56081                             if (label[p].setAttributes) {
56082                                 label[p].setAttributes({
56083                                     hidden: true
56084                                 }, true);
56085                             } else if(label[p].setVisible) {
56086                                 label[p].setVisible(false);
56087                             }
56088                         }
56089                     }
56090                 }
56091                 config.renderer(label, storeItem);
56092                 me.onPlaceCallout(label, storeItem, item, i, display, animate,
56093                                   j, count, previouslyPlacedCallouts);
56094                 previouslyPlacedCallouts.push(label);
56095                 count++;
56096             }
56097         }
56098         this.hideCallouts(count);
56099     },
56100
56101     onCreateCallout: function(storeItem, item, i, display) {
56102         var me = this,
56103             group = me.calloutsGroup,
56104             config = me.callouts,
56105             styles = config.styles,
56106             width = styles.width,
56107             height = styles.height,
56108             chart = me.chart,
56109             surface = chart.surface,
56110             calloutObj = {
56111                 //label: false,
56112                 //box: false,
56113                 lines: false
56114             };
56115
56116         calloutObj.lines = surface.add(Ext.apply({},
56117         {
56118             type: 'path',
56119             path: 'M0,0',
56120             stroke: me.getLegendColor() || '#555'
56121         },
56122         styles));
56123
56124         if (config.items) {
56125             calloutObj.panel = Ext.create('widget.panel', {
56126                 style: "position: absolute;",    
56127                 width: width,
56128                 height: height,
56129                 items: config.items,
56130                 renderTo: chart.el
56131             });
56132         }
56133
56134         return calloutObj;
56135     },
56136
56137     hideCallouts: function(index) {
56138         var calloutsArray = this.calloutsArray,
56139             len = calloutsArray.length,
56140             co,
56141             p;
56142         while (len-->index) {
56143             co = calloutsArray[len];
56144             for (p in co) {
56145                 if (co[p]) {
56146                     co[p].hide(true);
56147                 }
56148             }
56149         }
56150     }
56151 });
56152
56153 /**
56154  * @class Ext.draw.CompositeSprite
56155  * @extends Ext.util.MixedCollection
56156  *
56157  * A composite Sprite handles a group of sprites with common methods to a sprite
56158  * such as `hide`, `show`, `setAttributes`. These methods are applied to the set of sprites
56159  * added to the group.
56160  *
56161  * CompositeSprite extends {@link Ext.util.MixedCollection} so you can use the same methods
56162  * in `MixedCollection` to iterate through sprites, add and remove elements, etc.
56163  *
56164  * In order to create a CompositeSprite, one has to provide a handle to the surface where it is
56165  * rendered:
56166  *
56167  *     var group = Ext.create('Ext.draw.CompositeSprite', {
56168  *         surface: drawComponent.surface
56169  *     });
56170  *                  
56171  * Then just by using `MixedCollection` methods it's possible to add {@link Ext.draw.Sprite}s:
56172  *  
56173  *     group.add(sprite1);
56174  *     group.add(sprite2);
56175  *     group.add(sprite3);
56176  *                  
56177  * And then apply common Sprite methods to them:
56178  *  
56179  *     group.setAttributes({
56180  *         fill: '#f00'
56181  *     }, true);
56182  */
56183 Ext.define('Ext.draw.CompositeSprite', {
56184
56185     /* Begin Definitions */
56186
56187     extend: 'Ext.util.MixedCollection',
56188     mixins: {
56189         animate: 'Ext.util.Animate'
56190     },
56191
56192     /* End Definitions */
56193     isCompositeSprite: true,
56194     constructor: function(config) {
56195         var me = this;
56196         
56197         config = config || {};
56198         Ext.apply(me, config);
56199
56200         me.addEvents(
56201             'mousedown',
56202             'mouseup',
56203             'mouseover',
56204             'mouseout',
56205             'click'
56206         );
56207         me.id = Ext.id(null, 'ext-sprite-group-');
56208         me.callParent();
56209     },
56210
56211     // @private
56212     onClick: function(e) {
56213         this.fireEvent('click', e);
56214     },
56215
56216     // @private
56217     onMouseUp: function(e) {
56218         this.fireEvent('mouseup', e);
56219     },
56220
56221     // @private
56222     onMouseDown: function(e) {
56223         this.fireEvent('mousedown', e);
56224     },
56225
56226     // @private
56227     onMouseOver: function(e) {
56228         this.fireEvent('mouseover', e);
56229     },
56230
56231     // @private
56232     onMouseOut: function(e) {
56233         this.fireEvent('mouseout', e);
56234     },
56235
56236     attachEvents: function(o) {
56237         var me = this;
56238         
56239         o.on({
56240             scope: me,
56241             mousedown: me.onMouseDown,
56242             mouseup: me.onMouseUp,
56243             mouseover: me.onMouseOver,
56244             mouseout: me.onMouseOut,
56245             click: me.onClick
56246         });
56247     },
56248
56249     /** Add a Sprite to the Group */
56250     add: function(key, o) {
56251         var result = this.callParent(arguments);
56252         this.attachEvents(result);
56253         return result;
56254     },
56255
56256     insert: function(index, key, o) {
56257         return this.callParent(arguments);
56258     },
56259
56260     /** Remove a Sprite from the Group */
56261     remove: function(o) {
56262         var me = this;
56263         
56264         o.un({
56265             scope: me,
56266             mousedown: me.onMouseDown,
56267             mouseup: me.onMouseUp,
56268             mouseover: me.onMouseOver,
56269             mouseout: me.onMouseOut,
56270             click: me.onClick
56271         });
56272         me.callParent(arguments);
56273     },
56274     
56275     /**
56276      * Returns the group bounding box.
56277      * Behaves like {@link Ext.draw.Sprite} getBBox method.
56278     */
56279     getBBox: function() {
56280         var i = 0,
56281             sprite,
56282             bb,
56283             items = this.items,
56284             len = this.length,
56285             infinity = Infinity,
56286             minX = infinity,
56287             maxHeight = -infinity,
56288             minY = infinity,
56289             maxWidth = -infinity,
56290             maxWidthBBox, maxHeightBBox;
56291         
56292         for (; i < len; i++) {
56293             sprite = items[i];
56294             if (sprite.el) {
56295                 bb = sprite.getBBox();
56296                 minX = Math.min(minX, bb.x);
56297                 minY = Math.min(minY, bb.y);
56298                 maxHeight = Math.max(maxHeight, bb.height + bb.y);
56299                 maxWidth = Math.max(maxWidth, bb.width + bb.x);
56300             }
56301         }
56302         
56303         return {
56304             x: minX,
56305             y: minY,
56306             height: maxHeight - minY,
56307             width: maxWidth - minX
56308         };
56309     },
56310
56311     /**
56312      *  Iterates through all sprites calling
56313      *  `setAttributes` on each one. For more information
56314      *  {@link Ext.draw.Sprite} provides a description of the
56315      *  attributes that can be set with this method.
56316      */
56317     setAttributes: function(attrs, redraw) {
56318         var i = 0,
56319             items = this.items,
56320             len = this.length;
56321             
56322         for (; i < len; i++) {
56323             items[i].setAttributes(attrs, redraw);
56324         }
56325         return this;
56326     },
56327
56328     /**
56329      * Hides all sprites. If the first parameter of the method is true
56330      * then a redraw will be forced for each sprite.
56331      */
56332     hide: function(attrs) {
56333         var i = 0,
56334             items = this.items,
56335             len = this.length;
56336             
56337         for (; i < len; i++) {
56338             items[i].hide();
56339         }
56340         return this;
56341     },
56342
56343     /**
56344      * Shows all sprites. If the first parameter of the method is true
56345      * then a redraw will be forced for each sprite.
56346      */
56347     show: function(attrs) {
56348         var i = 0,
56349             items = this.items,
56350             len = this.length;
56351             
56352         for (; i < len; i++) {
56353             items[i].show();
56354         }
56355         return this;
56356     },
56357
56358     redraw: function() {
56359         var me = this,
56360             i = 0,
56361             items = me.items,
56362             surface = me.getSurface(),
56363             len = me.length;
56364         
56365         if (surface) {
56366             for (; i < len; i++) {
56367                 surface.renderItem(items[i]);
56368             }
56369         }
56370         return me;
56371     },
56372
56373     setStyle: function(obj) {
56374         var i = 0,
56375             items = this.items,
56376             len = this.length,
56377             item, el;
56378             
56379         for (; i < len; i++) {
56380             item = items[i];
56381             el = item.el;
56382             if (el) {
56383                 el.setStyle(obj);
56384             }
56385         }
56386     },
56387
56388     addCls: function(obj) {
56389         var i = 0,
56390             items = this.items,
56391             surface = this.getSurface(),
56392             len = this.length;
56393         
56394         if (surface) {
56395             for (; i < len; i++) {
56396                 surface.addCls(items[i], obj);
56397             }
56398         }
56399     },
56400
56401     removeCls: function(obj) {
56402         var i = 0,
56403             items = this.items,
56404             surface = this.getSurface(),
56405             len = this.length;
56406         
56407         if (surface) {
56408             for (; i < len; i++) {
56409                 surface.removeCls(items[i], obj);
56410             }
56411         }
56412     },
56413     
56414     /**
56415      * Grab the surface from the items
56416      * @private
56417      * @return {Ext.draw.Surface} The surface, null if not found
56418      */
56419     getSurface: function(){
56420         var first = this.first();
56421         if (first) {
56422             return first.surface;
56423         }
56424         return null;
56425     },
56426     
56427     /**
56428      * Destroys the SpriteGroup
56429      */
56430     destroy: function(){
56431         var me = this,
56432             surface = me.getSurface(),
56433             item;
56434             
56435         if (surface) {
56436             while (me.getCount() > 0) {
56437                 item = me.first();
56438                 me.remove(item);
56439                 surface.remove(item);
56440             }
56441         }
56442         me.clearListeners();
56443     }
56444 });
56445
56446 /**
56447  * @class Ext.layout.component.Draw
56448  * @extends Ext.layout.component.Component
56449  * @private
56450  *
56451  */
56452
56453 Ext.define('Ext.layout.component.Draw', {
56454
56455     /* Begin Definitions */
56456
56457     alias: 'layout.draw',
56458
56459     extend: 'Ext.layout.component.Auto',
56460
56461     /* End Definitions */
56462
56463     type: 'draw',
56464
56465     onLayout : function(width, height) {
56466         this.owner.surface.setSize(width, height);
56467         this.callParent(arguments);
56468     }
56469 });
56470 /**
56471  * @class Ext.chart.theme.Theme
56472  * @ignore
56473  */
56474 Ext.define('Ext.chart.theme.Theme', {
56475
56476     /* Begin Definitions */
56477
56478     requires: ['Ext.draw.Color'],
56479
56480     /* End Definitions */
56481
56482     theme: 'Base',
56483     themeAttrs: false,
56484     
56485     initTheme: function(theme) {
56486         var me = this,
56487             themes = Ext.chart.theme,
56488             key, gradients;
56489         if (theme) {
56490             theme = theme.split(':');
56491             for (key in themes) {
56492                 if (key == theme[0]) {
56493                     gradients = theme[1] == 'gradients';
56494                     me.themeAttrs = new themes[key]({
56495                         useGradients: gradients
56496                     });
56497                     if (gradients) {
56498                         me.gradients = me.themeAttrs.gradients;
56499                     }
56500                     if (me.themeAttrs.background) {
56501                         me.background = me.themeAttrs.background;
56502                     }
56503                     return;
56504                 }
56505             }
56506             Ext.Error.raise('No theme found named "' + theme + '"');
56507         }
56508     }
56509 }, 
56510 // This callback is executed right after when the class is created. This scope refers to the newly created class itself
56511 function() {
56512    /* Theme constructor: takes either a complex object with styles like:
56513   
56514    {
56515         axis: {
56516             fill: '#000',
56517             'stroke-width': 1
56518         },
56519         axisLabelTop: {
56520             fill: '#000',
56521             font: '11px Arial'
56522         },
56523         axisLabelLeft: {
56524             fill: '#000',
56525             font: '11px Arial'
56526         },
56527         axisLabelRight: {
56528             fill: '#000',
56529             font: '11px Arial'
56530         },
56531         axisLabelBottom: {
56532             fill: '#000',
56533             font: '11px Arial'
56534         },
56535         axisTitleTop: {
56536             fill: '#000',
56537             font: '11px Arial'
56538         },
56539         axisTitleLeft: {
56540             fill: '#000',
56541             font: '11px Arial'
56542         },
56543         axisTitleRight: {
56544             fill: '#000',
56545             font: '11px Arial'
56546         },
56547         axisTitleBottom: {
56548             fill: '#000',
56549             font: '11px Arial'
56550         },
56551         series: {
56552             'stroke-width': 1
56553         },
56554         seriesLabel: {
56555             font: '12px Arial',
56556             fill: '#333'
56557         },
56558         marker: {
56559             stroke: '#555',
56560             fill: '#000',
56561             radius: 3,
56562             size: 3
56563         },
56564         seriesThemes: [{
56565             fill: '#C6DBEF'
56566         }, {
56567             fill: '#9ECAE1'
56568         }, {
56569             fill: '#6BAED6'
56570         }, {
56571             fill: '#4292C6'
56572         }, {
56573             fill: '#2171B5'
56574         }, {
56575             fill: '#084594'
56576         }],
56577         markerThemes: [{
56578             fill: '#084594',
56579             type: 'circle' 
56580         }, {
56581             fill: '#2171B5',
56582             type: 'cross'
56583         }, {
56584             fill: '#4292C6',
56585             type: 'plus'
56586         }]
56587     }
56588   
56589   ...or also takes just an array of colors and creates the complex object:
56590   
56591   {
56592       colors: ['#aaa', '#bcd', '#eee']
56593   }
56594   
56595   ...or takes just a base color and makes a theme from it
56596   
56597   {
56598       baseColor: '#bce'
56599   }
56600   
56601   To create a new theme you may add it to the Themes object:
56602   
56603   Ext.chart.theme.MyNewTheme = Ext.extend(Object, {
56604       constructor: function(config) {
56605           Ext.chart.theme.call(this, config, {
56606               baseColor: '#mybasecolor'
56607           });
56608       }
56609   });
56610   
56611   //Proposal:
56612   Ext.chart.theme.MyNewTheme = Ext.chart.createTheme('#basecolor');
56613   
56614   ...and then to use it provide the name of the theme (as a lower case string) in the chart config.
56615   
56616   {
56617       theme: 'mynewtheme'
56618   }
56619  */
56620
56621 (function() {
56622     Ext.chart.theme = function(config, base) {
56623         config = config || {};
56624         var i = 0, l, colors, color,
56625             seriesThemes, markerThemes,
56626             seriesTheme, markerTheme, 
56627             key, gradients = [],
56628             midColor, midL;
56629         
56630         if (config.baseColor) {
56631             midColor = Ext.draw.Color.fromString(config.baseColor);
56632             midL = midColor.getHSL()[2];
56633             if (midL < 0.15) {
56634                 midColor = midColor.getLighter(0.3);
56635             } else if (midL < 0.3) {
56636                 midColor = midColor.getLighter(0.15);
56637             } else if (midL > 0.85) {
56638                 midColor = midColor.getDarker(0.3);
56639             } else if (midL > 0.7) {
56640                 midColor = midColor.getDarker(0.15);
56641             }
56642             config.colors = [ midColor.getDarker(0.3).toString(),
56643                               midColor.getDarker(0.15).toString(),
56644                               midColor.toString(),
56645                               midColor.getLighter(0.15).toString(),
56646                               midColor.getLighter(0.3).toString()];
56647
56648             delete config.baseColor;
56649         }
56650         if (config.colors) {
56651             colors = config.colors.slice();
56652             markerThemes = base.markerThemes;
56653             seriesThemes = base.seriesThemes;
56654             l = colors.length;
56655             base.colors = colors;
56656             for (; i < l; i++) {
56657                 color = colors[i];
56658                 markerTheme = markerThemes[i] || {};
56659                 seriesTheme = seriesThemes[i] || {};
56660                 markerTheme.fill = seriesTheme.fill = markerTheme.stroke = seriesTheme.stroke = color;
56661                 markerThemes[i] = markerTheme;
56662                 seriesThemes[i] = seriesTheme;
56663             }
56664             base.markerThemes = markerThemes.slice(0, l);
56665             base.seriesThemes = seriesThemes.slice(0, l);
56666         //the user is configuring something in particular (either markers, series or pie slices)
56667         }
56668         for (key in base) {
56669             if (key in config) {
56670                 if (Ext.isObject(config[key]) && Ext.isObject(base[key])) {
56671                     Ext.apply(base[key], config[key]);
56672                 } else {
56673                     base[key] = config[key];
56674                 }
56675             }
56676         }
56677         if (config.useGradients) {
56678             colors = base.colors || (function () {
56679                 var ans = [];
56680                 for (i = 0, seriesThemes = base.seriesThemes, l = seriesThemes.length; i < l; i++) {
56681                     ans.push(seriesThemes[i].fill || seriesThemes[i].stroke);
56682                 }
56683                 return ans;
56684             })();
56685             for (i = 0, l = colors.length; i < l; i++) {
56686                 midColor = Ext.draw.Color.fromString(colors[i]);
56687                 if (midColor) {
56688                     color = midColor.getDarker(0.1).toString();
56689                     midColor = midColor.toString();
56690                     key = 'theme-' + midColor.substr(1) + '-' + color.substr(1);
56691                     gradients.push({
56692                         id: key,
56693                         angle: 45,
56694                         stops: {
56695                             0: {
56696                                 color: midColor.toString()
56697                             },
56698                             100: {
56699                                 color: color.toString()
56700                             }
56701                         }
56702                     });
56703                     colors[i] = 'url(#' + key + ')'; 
56704                 }
56705             }
56706             base.gradients = gradients;
56707             base.colors = colors;
56708         }
56709         /*
56710         base.axis = Ext.apply(base.axis || {}, config.axis || {});
56711         base.axisLabel = Ext.apply(base.axisLabel || {}, config.axisLabel || {});
56712         base.axisTitle = Ext.apply(base.axisTitle || {}, config.axisTitle || {});
56713         */
56714         Ext.apply(this, base);
56715     };
56716 })();
56717 });
56718
56719 /**
56720  * @class Ext.chart.Mask
56721  *
56722  * Defines a mask for a chart's series.
56723  * The 'chart' member must be set prior to rendering.
56724  *
56725  * A Mask can be used to select a certain region in a chart.
56726  * When enabled, the `select` event will be triggered when a
56727  * region is selected by the mask, allowing the user to perform
56728  * other tasks like zooming on that region, etc.
56729  *
56730  * In order to use the mask one has to set the Chart `mask` option to
56731  * `true`, `vertical` or `horizontal`. Then a possible configuration for the
56732  * listener could be:
56733  *
56734         items: {
56735             xtype: 'chart',
56736             animate: true,
56737             store: store1,
56738             mask: 'horizontal',
56739             listeners: {
56740                 select: {
56741                     fn: function(me, selection) {
56742                         me.setZoom(selection);
56743                         me.mask.hide();
56744                     }
56745                 }
56746             },
56747
56748  * In this example we zoom the chart to that particular region. You can also get
56749  * a handle to a mask instance from the chart object. The `chart.mask` element is a
56750  * `Ext.Panel`.
56751  * 
56752  * @constructor
56753  */
56754 Ext.define('Ext.chart.Mask', {
56755     constructor: function(config) {
56756         var me = this;
56757
56758         me.addEvents('select');
56759
56760         if (config) {
56761             Ext.apply(me, config);
56762         }
56763         if (me.mask) {
56764             me.on('afterrender', function() {
56765                 //create a mask layer component
56766                 var comp = Ext.create('Ext.chart.MaskLayer', {
56767                     renderTo: me.el
56768                 });
56769                 comp.el.on({
56770                     'mousemove': function(e) {
56771                         me.onMouseMove(e);
56772                     },
56773                     'mouseup': function(e) {
56774                         me.resized(e);
56775                     }
56776                 });
56777                 //create a resize handler for the component
56778                 var resizeHandler = Ext.create('Ext.resizer.Resizer', {
56779                     el: comp.el,
56780                     handles: 'all',
56781                     pinned: true
56782                 });
56783                 resizeHandler.on({
56784                     'resize': function(e) {
56785                         me.resized(e);    
56786                     }    
56787                 });
56788                 comp.initDraggable();
56789                 me.maskType = me.mask;
56790                 me.mask = comp;
56791                 me.maskSprite = me.surface.add({
56792                     type: 'path',
56793                     path: ['M', 0, 0],
56794                     zIndex: 1001,
56795                     opacity: 0.7,
56796                     hidden: true,
56797                     stroke: '#444'
56798                 });
56799             }, me, { single: true });
56800         }
56801     },
56802     
56803     resized: function(e) {
56804         var me = this,
56805             bbox = me.bbox || me.chartBBox,
56806             x = bbox.x,
56807             y = bbox.y,
56808             width = bbox.width,
56809             height = bbox.height,
56810             box = me.mask.getBox(true),
56811             max = Math.max,
56812             min = Math.min,
56813             staticX = box.x - x,
56814             staticY = box.y - y;
56815         
56816         staticX = max(staticX, x);
56817         staticY = max(staticY, y);
56818         staticX = min(staticX, width);
56819         staticY = min(staticY, height);
56820         box.x = staticX;
56821         box.y = staticY;
56822         me.fireEvent('select', me, box);
56823     },
56824
56825     onMouseUp: function(e) {
56826         var me = this,
56827             bbox = me.bbox || me.chartBBox,
56828             sel = me.maskSelection;
56829         me.maskMouseDown = false;
56830         me.mouseDown = false;
56831         if (me.mouseMoved) {
56832             me.onMouseMove(e);
56833             me.mouseMoved = false;
56834             me.fireEvent('select', me, {
56835                 x: sel.x - bbox.x,
56836                 y: sel.y - bbox.y,
56837                 width: sel.width,
56838                 height: sel.height
56839             });
56840         }
56841     },
56842
56843     onMouseDown: function(e) {
56844         var me = this;
56845         me.mouseDown = true;
56846         me.mouseMoved = false;
56847         me.maskMouseDown = {
56848             x: e.getPageX() - me.el.getX(),
56849             y: e.getPageY() - me.el.getY()
56850         };
56851     },
56852
56853     onMouseMove: function(e) {
56854         var me = this,
56855             mask = me.maskType,
56856             bbox = me.bbox || me.chartBBox,
56857             x = bbox.x,
56858             y = bbox.y,
56859             math = Math,
56860             floor = math.floor,
56861             abs = math.abs,
56862             min = math.min,
56863             max = math.max,
56864             height = floor(y + bbox.height),
56865             width = floor(x + bbox.width),
56866             posX = e.getPageX(),
56867             posY = e.getPageY(),
56868             staticX = posX - me.el.getX(),
56869             staticY = posY - me.el.getY(),
56870             maskMouseDown = me.maskMouseDown,
56871             path;
56872         
56873         me.mouseMoved = me.mouseDown;
56874         staticX = max(staticX, x);
56875         staticY = max(staticY, y);
56876         staticX = min(staticX, width);
56877         staticY = min(staticY, height);
56878         if (maskMouseDown && me.mouseDown) {
56879             if (mask == 'horizontal') {
56880                 staticY = y;
56881                 maskMouseDown.y = height;
56882                 posY = me.el.getY() + bbox.height + me.insetPadding;
56883             }
56884             else if (mask == 'vertical') {
56885                 staticX = x;
56886                 maskMouseDown.x = width;
56887             }
56888             width = maskMouseDown.x - staticX;
56889             height = maskMouseDown.y - staticY;
56890             path = ['M', staticX, staticY, 'l', width, 0, 0, height, -width, 0, 'z'];
56891             me.maskSelection = {
56892                 x: width > 0 ? staticX : staticX + width,
56893                 y: height > 0 ? staticY : staticY + height,
56894                 width: abs(width),
56895                 height: abs(height)
56896             };
56897             me.mask.updateBox({
56898                 x: posX - abs(width),
56899                 y: posY - abs(height),
56900                 width: abs(width),
56901                 height: abs(height)
56902             });
56903             me.mask.show();
56904             me.maskSprite.setAttributes({
56905                 hidden: true    
56906             }, true);
56907         }
56908         else {
56909             if (mask == 'horizontal') {
56910                 path = ['M', staticX, y, 'L', staticX, height];
56911             }
56912             else if (mask == 'vertical') {
56913                 path = ['M', x, staticY, 'L', width, staticY];
56914             }
56915             else {
56916                 path = ['M', staticX, y, 'L', staticX, height, 'M', x, staticY, 'L', width, staticY];
56917             }
56918             me.maskSprite.setAttributes({
56919                 path: path,
56920                 fill: me.maskMouseDown ? me.maskSprite.stroke : false,
56921                 'stroke-width': mask === true ? 1 : 3,
56922                 hidden: false
56923             }, true);
56924         }
56925     },
56926
56927     onMouseLeave: function(e) {
56928         var me = this;
56929         me.mouseMoved = false;
56930         me.mouseDown = false;
56931         me.maskMouseDown = false;
56932         me.mask.hide();
56933         me.maskSprite.hide(true);
56934     }
56935 });
56936     
56937 /**
56938  * @class Ext.chart.Navigation
56939  *
56940  * Handles panning and zooming capabilities.
56941  * 
56942  * @ignore
56943  */
56944 Ext.define('Ext.chart.Navigation', {
56945
56946     constructor: function() {
56947         this.originalStore = this.store;
56948     },
56949     
56950     //filters the store to the specified interval(s)
56951     setZoom: function(zoomConfig) {
56952         var me = this,
56953             store = me.substore || me.store,
56954             bbox = me.chartBBox,
56955             len = store.getCount(),
56956             from = (zoomConfig.x / bbox.width * len) >> 0,
56957             to = Math.ceil(((zoomConfig.x + zoomConfig.width) / bbox.width * len)),
56958             recFieldsLen, recFields = [], curField, json = [], obj;
56959         
56960         store.each(function(rec, i) {
56961             if (i < from || i > to) {
56962                 return;
56963             }
56964             obj = {};
56965             //get all record field names in a simple array
56966             if (!recFields.length) {
56967                 rec.fields.each(function(f) {
56968                     recFields.push(f.name);
56969                 });
56970                 recFieldsLen = recFields.length;
56971             }
56972             //append record values to an aggregation record
56973             for (i = 0; i < recFieldsLen; i++) {
56974                 curField = recFields[i];
56975                 obj[curField] = rec.get(curField);
56976             }
56977             json.push(obj);
56978         });
56979         me.store = me.substore = Ext.create('Ext.data.JsonStore', {
56980             fields: recFields,
56981             data: json
56982         });
56983         me.redraw(true);
56984     },
56985
56986     restoreZoom: function() {
56987         this.store = this.substore = this.originalStore;
56988         this.redraw(true);
56989     }
56990     
56991 });
56992 /**
56993  * @class Ext.chart.Shape
56994  * @ignore
56995  */
56996 Ext.define('Ext.chart.Shape', {
56997
56998     /* Begin Definitions */
56999
57000     singleton: true,
57001
57002     /* End Definitions */
57003
57004     circle: function (surface, opts) {
57005         return surface.add(Ext.apply({
57006             type: 'circle',
57007             x: opts.x,
57008             y: opts.y,
57009             stroke: null,
57010             radius: opts.radius
57011         }, opts));
57012     },
57013     line: function (surface, opts) {
57014         return surface.add(Ext.apply({
57015             type: 'rect',
57016             x: opts.x - opts.radius,
57017             y: opts.y - opts.radius,
57018             height: 2 * opts.radius,
57019             width: 2 * opts.radius / 5
57020         }, opts));
57021     },
57022     square: function (surface, opts) {
57023         return surface.add(Ext.applyIf({
57024             type: 'rect',
57025             x: opts.x - opts.radius,
57026             y: opts.y - opts.radius,
57027             height: 2 * opts.radius,
57028             width: 2 * opts.radius,
57029             radius: null
57030         }, opts));
57031     },
57032     triangle: function (surface, opts) {
57033         opts.radius *= 1.75;
57034         return surface.add(Ext.apply({
57035             type: 'path',
57036             stroke: null,
57037             path: "M".concat(opts.x, ",", opts.y, "m0-", opts.radius * 0.58, "l", opts.radius * 0.5, ",", opts.radius * 0.87, "-", opts.radius, ",0z")
57038         }, opts));
57039     },
57040     diamond: function (surface, opts) {
57041         var r = opts.radius;
57042         r *= 1.5;
57043         return surface.add(Ext.apply({
57044             type: 'path',
57045             stroke: null,
57046             path: ["M", opts.x, opts.y - r, "l", r, r, -r, r, -r, -r, r, -r, "z"]
57047         }, opts));
57048     },
57049     cross: function (surface, opts) {
57050         var r = opts.radius;
57051         r = r / 1.7;
57052         return surface.add(Ext.apply({
57053             type: 'path',
57054             stroke: null,
57055             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"])
57056         }, opts));
57057     },
57058     plus: function (surface, opts) {
57059         var r = opts.radius / 1.3;
57060         return surface.add(Ext.apply({
57061             type: 'path',
57062             stroke: null,
57063             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"])
57064         }, opts));
57065     },
57066     arrow: function (surface, opts) {
57067         var r = opts.radius;
57068         return surface.add(Ext.apply({
57069             type: 'path',
57070             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")
57071         }, opts));
57072     },
57073     drop: function (surface, x, y, text, size, angle) {
57074         size = size || 30;
57075         angle = angle || 0;
57076         surface.add({
57077             type: 'path',
57078             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'],
57079             fill: '#000',
57080             stroke: 'none',
57081             rotate: {
57082                 degrees: 22.5 - angle,
57083                 x: x,
57084                 y: y
57085             }
57086         });
57087         angle = (angle + 90) * Math.PI / 180;
57088         surface.add({
57089             type: 'text',
57090             x: x + size * Math.sin(angle) - 10, // Shift here, Not sure why.
57091             y: y + size * Math.cos(angle) + 5,
57092             text:  text,
57093             'font-size': size * 12 / 40,
57094             stroke: 'none',
57095             fill: '#fff'
57096         });
57097     }
57098 });
57099 /**
57100  * @class Ext.draw.Surface
57101  * @extends Object
57102  *
57103  * A Surface is an interface to render methods inside a draw {@link Ext.draw.Component}.
57104  * A Surface contains methods to render sprites, get bounding boxes of sprites, add
57105  * sprites to the canvas, initialize other graphic components, etc. One of the most used
57106  * methods for this class is the `add` method, to add Sprites to the surface.
57107  *
57108  * Most of the Surface methods are abstract and they have a concrete implementation
57109  * in VML or SVG engines.
57110  *
57111  * A Surface instance can be accessed as a property of a draw component. For example:
57112  *
57113  *     drawComponent.surface.add({
57114  *         type: 'circle',
57115  *         fill: '#ffc',
57116  *         radius: 100,
57117  *         x: 100,
57118  *         y: 100
57119  *     });
57120  *
57121  * The configuration object passed in the `add` method is the same as described in the {@link Ext.draw.Sprite}
57122  * class documentation.
57123  *
57124  * ### Listeners
57125  *
57126  * You can also add event listeners to the surface using the `Observable` listener syntax. Supported events are:
57127  *
57128  * - mousedown
57129  * - mouseup
57130  * - mouseover
57131  * - mouseout
57132  * - mousemove
57133  * - mouseenter
57134  * - mouseleave
57135  * - click
57136  *
57137  * For example:
57138  *
57139         drawComponent.surface.on({
57140            'mousemove': function() {
57141                 console.log('moving the mouse over the surface');   
57142             }
57143         });
57144  */
57145 Ext.define('Ext.draw.Surface', {
57146
57147     /* Begin Definitions */
57148
57149     mixins: {
57150         observable: 'Ext.util.Observable'
57151     },
57152
57153     requires: ['Ext.draw.CompositeSprite'],
57154     uses: ['Ext.draw.engine.Svg', 'Ext.draw.engine.Vml'],
57155
57156     separatorRe: /[, ]+/,
57157
57158     statics: {
57159         /**
57160          * Create and return a new concrete Surface instance appropriate for the current environment.
57161          * @param {Object} config Initial configuration for the Surface instance
57162          * @param {Array} enginePriority Optional order of implementations to use; the first one that is
57163          *                available in the current environment will be used. Defaults to
57164          *                <code>['Svg', 'Vml']</code>.
57165          */
57166         create: function(config, enginePriority) {
57167             enginePriority = enginePriority || ['Svg', 'Vml'];
57168
57169             var i = 0,
57170                 len = enginePriority.length,
57171                 surfaceClass;
57172
57173             for (; i < len; i++) {
57174                 if (Ext.supports[enginePriority[i]]) {
57175                     return Ext.create('Ext.draw.engine.' + enginePriority[i], config);
57176                 }
57177             }
57178             return false;
57179         }
57180     },
57181
57182     /* End Definitions */
57183
57184     // @private
57185     availableAttrs: {
57186         blur: 0,
57187         "clip-rect": "0 0 1e9 1e9",
57188         cursor: "default",
57189         cx: 0,
57190         cy: 0,
57191         'dominant-baseline': 'auto',
57192         fill: "none",
57193         "fill-opacity": 1,
57194         font: '10px "Arial"',
57195         "font-family": '"Arial"',
57196         "font-size": "10",
57197         "font-style": "normal",
57198         "font-weight": 400,
57199         gradient: "",
57200         height: 0,
57201         hidden: false,
57202         href: "http://sencha.com/",
57203         opacity: 1,
57204         path: "M0,0",
57205         radius: 0,
57206         rx: 0,
57207         ry: 0,
57208         scale: "1 1",
57209         src: "",
57210         stroke: "#000",
57211         "stroke-dasharray": "",
57212         "stroke-linecap": "butt",
57213         "stroke-linejoin": "butt",
57214         "stroke-miterlimit": 0,
57215         "stroke-opacity": 1,
57216         "stroke-width": 1,
57217         target: "_blank",
57218         text: "",
57219         "text-anchor": "middle",
57220         title: "Ext Draw",
57221         width: 0,
57222         x: 0,
57223         y: 0,
57224         zIndex: 0
57225     },
57226
57227  /**
57228   * @cfg {Number} height
57229   * The height of this component in pixels (defaults to auto).
57230   * <b>Note</b> to express this dimension as a percentage or offset see {@link Ext.Component#anchor}.
57231   */
57232  /**
57233   * @cfg {Number} width
57234   * The width of this component in pixels (defaults to auto).
57235   * <b>Note</b> to express this dimension as a percentage or offset see {@link Ext.Component#anchor}.
57236   */
57237     container: undefined,
57238     height: 352,
57239     width: 512,
57240     x: 0,
57241     y: 0,
57242
57243     constructor: function(config) {
57244         var me = this;
57245         config = config || {};
57246         Ext.apply(me, config);
57247
57248         me.domRef = Ext.getDoc().dom;
57249         
57250         me.customAttributes = {};
57251
57252         me.addEvents(
57253             'mousedown',
57254             'mouseup',
57255             'mouseover',
57256             'mouseout',
57257             'mousemove',
57258             'mouseenter',
57259             'mouseleave',
57260             'click'
57261         );
57262
57263         me.mixins.observable.constructor.call(me);
57264
57265         me.getId();
57266         me.initGradients();
57267         me.initItems();
57268         if (me.renderTo) {
57269             me.render(me.renderTo);
57270             delete me.renderTo;
57271         }
57272         me.initBackground(config.background);
57273     },
57274
57275     // @private called to initialize components in the surface
57276     // this is dependent on the underlying implementation.
57277     initSurface: Ext.emptyFn,
57278
57279     // @private called to setup the surface to render an item
57280     //this is dependent on the underlying implementation.
57281     renderItem: Ext.emptyFn,
57282
57283     // @private
57284     renderItems: Ext.emptyFn,
57285
57286     // @private
57287     setViewBox: Ext.emptyFn,
57288
57289     /**
57290      * Adds one or more CSS classes to the element. Duplicate classes are automatically filtered out.
57291      *
57292      * For example:
57293      *
57294      *          drawComponent.surface.addCls(sprite, 'x-visible');
57295      *      
57296      * @param {Object} sprite The sprite to add the class to.
57297      * @param {String/Array} className The CSS class to add, or an array of classes
57298      */
57299     addCls: Ext.emptyFn,
57300
57301     /**
57302      * Removes one or more CSS classes from the element.
57303      *
57304      * For example:
57305      *
57306      *      drawComponent.surface.removeCls(sprite, 'x-visible');
57307      *      
57308      * @param {Object} sprite The sprite to remove the class from.
57309      * @param {String/Array} className The CSS class to remove, or an array of classes
57310      */
57311     removeCls: Ext.emptyFn,
57312
57313     /**
57314      * Sets CSS style attributes to an element.
57315      *
57316      * For example:
57317      *
57318      *      drawComponent.surface.setStyle(sprite, {
57319      *          'cursor': 'pointer'
57320      *      });
57321      *      
57322      * @param {Object} sprite The sprite to add, or an array of classes to
57323      * @param {Object} styles An Object with CSS styles.
57324      */
57325     setStyle: Ext.emptyFn,
57326
57327     // @private
57328     initGradients: function() {
57329         var gradients = this.gradients;
57330         if (gradients) {
57331             Ext.each(gradients, this.addGradient, this);
57332         }
57333     },
57334
57335     // @private
57336     initItems: function() {
57337         var items = this.items;
57338         this.items = Ext.create('Ext.draw.CompositeSprite');
57339         this.groups = Ext.create('Ext.draw.CompositeSprite');
57340         if (items) {
57341             this.add(items);
57342         }
57343     },
57344     
57345     // @private
57346     initBackground: function(config) {
57347         var gradientId, 
57348             gradient,
57349             backgroundSprite,
57350             width = this.width,
57351             height = this.height;
57352         if (config) {
57353             if (config.gradient) {
57354                 gradient = config.gradient;
57355                 gradientId = gradient.id;
57356                 this.addGradient(gradient);
57357                 this.background = this.add({
57358                     type: 'rect',
57359                     x: 0,
57360                     y: 0,
57361                     width: width,
57362                     height: height,
57363                     fill: 'url(#' + gradientId + ')'
57364                 });
57365             } else if (config.fill) {
57366                 this.background = this.add({
57367                     type: 'rect',
57368                     x: 0,
57369                     y: 0,
57370                     width: width,
57371                     height: height,
57372                     fill: config.fill
57373                 });
57374             } else if (config.image) {
57375                 this.background = this.add({
57376                     type: 'image',
57377                     x: 0,
57378                     y: 0,
57379                     width: width,
57380                     height: height,
57381                     src: config.image
57382                 });
57383             }
57384         }
57385     },
57386     
57387     /**
57388      * Sets the size of the surface. Accomodates the background (if any) to fit the new size too.
57389      *
57390      * For example:
57391      *
57392      *      drawComponent.surface.setSize(500, 500);
57393      *
57394      * This method is generally called when also setting the size of the draw Component.
57395      * 
57396      * @param {Number} w The new width of the canvas.
57397      * @param {Number} h The new height of the canvas.
57398      */
57399     setSize: function(w, h) {
57400         if (this.background) {
57401             this.background.setAttributes({
57402                 width: w,
57403                 height: h,
57404                 hidden: false
57405             }, true);
57406         }
57407     },
57408
57409     // @private
57410     scrubAttrs: function(sprite) {
57411         var i,
57412             attrs = {},
57413             exclude = {},
57414             sattr = sprite.attr;
57415         for (i in sattr) {    
57416             // Narrow down attributes to the main set
57417             if (this.translateAttrs.hasOwnProperty(i)) {
57418                 // Translated attr
57419                 attrs[this.translateAttrs[i]] = sattr[i];
57420                 exclude[this.translateAttrs[i]] = true;
57421             }
57422             else if (this.availableAttrs.hasOwnProperty(i) && !exclude[i]) {
57423                 // Passtrhough attr
57424                 attrs[i] = sattr[i];
57425             }
57426         }
57427         return attrs;
57428     },
57429
57430     // @private
57431     onClick: function(e) {
57432         this.processEvent('click', e);
57433     },
57434
57435     // @private
57436     onMouseUp: function(e) {
57437         this.processEvent('mouseup', e);
57438     },
57439
57440     // @private
57441     onMouseDown: function(e) {
57442         this.processEvent('mousedown', e);
57443     },
57444
57445     // @private
57446     onMouseOver: function(e) {
57447         this.processEvent('mouseover', e);
57448     },
57449
57450     // @private
57451     onMouseOut: function(e) {
57452         this.processEvent('mouseout', e);
57453     },
57454
57455     // @private
57456     onMouseMove: function(e) {
57457         this.fireEvent('mousemove', e);
57458     },
57459
57460     // @private
57461     onMouseEnter: Ext.emptyFn,
57462
57463     // @private
57464     onMouseLeave: Ext.emptyFn,
57465
57466     /**
57467      * Add a gradient definition to the Surface. Note that in some surface engines, adding
57468      * a gradient via this method will not take effect if the surface has already been rendered.
57469      * Therefore, it is preferred to pass the gradients as an item to the surface config, rather
57470      * than calling this method, especially if the surface is rendered immediately (e.g. due to
57471      * 'renderTo' in its config). For more information on how to create gradients in the Chart
57472      * configuration object please refer to {@link Ext.chart.Chart}.
57473      *
57474      * The gradient object to be passed into this method is composed by:
57475      * 
57476      * 
57477      *  - **id** - string - The unique name of the gradient.
57478      *  - **angle** - number, optional - The angle of the gradient in degrees.
57479      *  - **stops** - object - An object with numbers as keys (from 0 to 100) and style objects as values.
57480      * 
57481      *
57482      For example:
57483                 drawComponent.surface.addGradient({
57484                     id: 'gradientId',
57485                     angle: 45,
57486                     stops: {
57487                         0: {
57488                             color: '#555'
57489                         },
57490                         100: {
57491                             color: '#ddd'
57492                         }
57493                     }
57494                 });
57495      */
57496     addGradient: Ext.emptyFn,
57497
57498     /**
57499      * Add a Sprite to the surface. See {@link Ext.draw.Sprite} for the configuration object to be passed into this method.
57500      *
57501      * For example:
57502      *
57503      *     drawComponent.surface.add({
57504      *         type: 'circle',
57505      *         fill: '#ffc',
57506      *         radius: 100,
57507      *         x: 100,
57508      *         y: 100
57509      *     });
57510      *
57511     */
57512     add: function() {
57513         var args = Array.prototype.slice.call(arguments),
57514             sprite,
57515             index;
57516
57517         var hasMultipleArgs = args.length > 1;
57518         if (hasMultipleArgs || Ext.isArray(args[0])) {
57519             var items = hasMultipleArgs ? args : args[0],
57520                 results = [],
57521                 i, ln, item;
57522
57523             for (i = 0, ln = items.length; i < ln; i++) {
57524                 item = items[i];
57525                 item = this.add(item);
57526                 results.push(item);
57527             }
57528
57529             return results;
57530         }
57531         sprite = this.prepareItems(args[0], true)[0];
57532         this.normalizeSpriteCollection(sprite);
57533         this.onAdd(sprite);
57534         return sprite;
57535     },
57536
57537     /**
57538      * @private
57539      * Insert or move a given sprite into the correct position in the items
57540      * MixedCollection, according to its zIndex. Will be inserted at the end of
57541      * an existing series of sprites with the same or lower zIndex. If the sprite
57542      * is already positioned within an appropriate zIndex group, it will not be moved.
57543      * This ordering can be used by subclasses to assist in rendering the sprites in
57544      * the correct order for proper z-index stacking.
57545      * @param {Ext.draw.Sprite} sprite
57546      * @return {Number} the sprite's new index in the list
57547      */
57548     normalizeSpriteCollection: function(sprite) {
57549         var items = this.items,
57550             zIndex = sprite.attr.zIndex,
57551             idx = items.indexOf(sprite);
57552
57553         if (idx < 0 || (idx > 0 && items.getAt(idx - 1).attr.zIndex > zIndex) ||
57554                 (idx < items.length - 1 && items.getAt(idx + 1).attr.zIndex < zIndex)) {
57555             items.removeAt(idx);
57556             idx = items.findIndexBy(function(otherSprite) {
57557                 return otherSprite.attr.zIndex > zIndex;
57558             });
57559             if (idx < 0) {
57560                 idx = items.length;
57561             }
57562             items.insert(idx, sprite);
57563         }
57564         return idx;
57565     },
57566
57567     onAdd: function(sprite) {
57568         var group = sprite.group,
57569             draggable = sprite.draggable,
57570             groups, ln, i;
57571         if (group) {
57572             groups = [].concat(group);
57573             ln = groups.length;
57574             for (i = 0; i < ln; i++) {
57575                 group = groups[i];
57576                 this.getGroup(group).add(sprite);
57577             }
57578             delete sprite.group;
57579         }
57580         if (draggable) {
57581             sprite.initDraggable();
57582         }
57583     },
57584
57585     /**
57586      * Remove a given sprite from the surface, optionally destroying the sprite in the process.
57587      * You can also call the sprite own `remove` method.
57588      *
57589      * For example:
57590      *
57591      *      drawComponent.surface.remove(sprite);
57592      *      //or...
57593      *      sprite.remove();
57594      *      
57595      * @param {Ext.draw.Sprite} sprite
57596      * @param {Boolean} destroySprite
57597      * @return {Number} the sprite's new index in the list
57598      */
57599     remove: function(sprite, destroySprite) {
57600         if (sprite) {
57601             this.items.remove(sprite);
57602             this.groups.each(function(item) {
57603                 item.remove(sprite);
57604             });
57605             sprite.onRemove();
57606             if (destroySprite === true) {
57607                 sprite.destroy();
57608             }
57609         }
57610     },
57611
57612     /**
57613      * Remove all sprites from the surface, optionally destroying the sprites in the process.
57614      *
57615      * For example:
57616      *
57617      *      drawComponent.surface.removeAll();
57618      *      
57619      * @param {Boolean} destroySprites Whether to destroy all sprites when removing them.
57620      * @return {Number} The sprite's new index in the list.
57621      */
57622     removeAll: function(destroySprites) {
57623         var items = this.items.items,
57624             ln = items.length,
57625             i;
57626         for (i = ln - 1; i > -1; i--) {
57627             this.remove(items[i], destroySprites);
57628         }
57629     },
57630
57631     onRemove: Ext.emptyFn,
57632
57633     onDestroy: Ext.emptyFn,
57634
57635     // @private
57636     applyTransformations: function(sprite) {
57637             sprite.bbox.transform = 0;
57638             this.transform(sprite);
57639
57640         var me = this,
57641             dirty = false,
57642             attr = sprite.attr;
57643
57644         if (attr.translation.x != null || attr.translation.y != null) {
57645             me.translate(sprite);
57646             dirty = true;
57647         }
57648         if (attr.scaling.x != null || attr.scaling.y != null) {
57649             me.scale(sprite);
57650             dirty = true;
57651         }
57652         if (attr.rotation.degrees != null) {
57653             me.rotate(sprite);
57654             dirty = true;
57655         }
57656         if (dirty) {
57657             sprite.bbox.transform = 0;
57658             this.transform(sprite);
57659             sprite.transformations = [];
57660         }
57661     },
57662
57663     // @private
57664     rotate: function (sprite) {
57665         var bbox,
57666             deg = sprite.attr.rotation.degrees,
57667             centerX = sprite.attr.rotation.x,
57668             centerY = sprite.attr.rotation.y;
57669         if (!Ext.isNumber(centerX) || !Ext.isNumber(centerY)) {
57670             bbox = this.getBBox(sprite);
57671             centerX = !Ext.isNumber(centerX) ? bbox.x + bbox.width / 2 : centerX;
57672             centerY = !Ext.isNumber(centerY) ? bbox.y + bbox.height / 2 : centerY;
57673         }
57674         sprite.transformations.push({
57675             type: "rotate",
57676             degrees: deg,
57677             x: centerX,
57678             y: centerY
57679         });
57680     },
57681
57682     // @private
57683     translate: function(sprite) {
57684         var x = sprite.attr.translation.x || 0,
57685             y = sprite.attr.translation.y || 0;
57686         sprite.transformations.push({
57687             type: "translate",
57688             x: x,
57689             y: y
57690         });
57691     },
57692
57693     // @private
57694     scale: function(sprite) {
57695         var bbox,
57696             x = sprite.attr.scaling.x || 1,
57697             y = sprite.attr.scaling.y || 1,
57698             centerX = sprite.attr.scaling.centerX,
57699             centerY = sprite.attr.scaling.centerY;
57700
57701         if (!Ext.isNumber(centerX) || !Ext.isNumber(centerY)) {
57702             bbox = this.getBBox(sprite);
57703             centerX = !Ext.isNumber(centerX) ? bbox.x + bbox.width / 2 : centerX;
57704             centerY = !Ext.isNumber(centerY) ? bbox.y + bbox.height / 2 : centerY;
57705         }
57706         sprite.transformations.push({
57707             type: "scale",
57708             x: x,
57709             y: y,
57710             centerX: centerX,
57711             centerY: centerY
57712         });
57713     },
57714
57715     // @private
57716     rectPath: function (x, y, w, h, r) {
57717         if (r) {
57718             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"]];
57719         }
57720         return [["M", x, y], ["l", w, 0], ["l", 0, h], ["l", -w, 0], ["z"]];
57721     },
57722
57723     // @private
57724     ellipsePath: function (x, y, rx, ry) {
57725         if (ry == null) {
57726             ry = rx;
57727         }
57728         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"]];
57729     },
57730
57731     // @private
57732     getPathpath: function (el) {
57733         return el.attr.path;
57734     },
57735
57736     // @private
57737     getPathcircle: function (el) {
57738         var a = el.attr;
57739         return this.ellipsePath(a.x, a.y, a.radius, a.radius);
57740     },
57741
57742     // @private
57743     getPathellipse: function (el) {
57744         var a = el.attr;
57745         return this.ellipsePath(a.x, a.y, a.radiusX, a.radiusY);
57746     },
57747
57748     // @private
57749     getPathrect: function (el) {
57750         var a = el.attr;
57751         return this.rectPath(a.x, a.y, a.width, a.height, a.r);
57752     },
57753
57754     // @private
57755     getPathimage: function (el) {
57756         var a = el.attr;
57757         return this.rectPath(a.x || 0, a.y || 0, a.width, a.height);
57758     },
57759
57760     // @private
57761     getPathtext: function (el) {
57762         var bbox = this.getBBoxText(el);
57763         return this.rectPath(bbox.x, bbox.y, bbox.width, bbox.height);
57764     },
57765
57766     createGroup: function(id) {
57767         var group = this.groups.get(id);
57768         if (!group) {
57769             group = Ext.create('Ext.draw.CompositeSprite', {
57770                 surface: this
57771             });
57772             group.id = id || Ext.id(null, 'ext-surface-group-');
57773             this.groups.add(group);
57774         }
57775         return group;
57776     },
57777
57778     /**
57779      * Returns a new group or an existent group associated with the current surface.
57780      * The group returned is a {@link Ext.draw.CompositeSprite} group.
57781      *
57782      * For example:
57783      *
57784      *      var spriteGroup = drawComponent.surface.getGroup('someGroupId');
57785      *      
57786      * @param {String} id The unique identifier of the group.
57787      * @return {Object} The {@link Ext.draw.CompositeSprite}.
57788      */
57789     getGroup: function(id) {
57790         if (typeof id == "string") {
57791             var group = this.groups.get(id);
57792             if (!group) {
57793                 group = this.createGroup(id);
57794             }
57795         } else {
57796             group = id;
57797         }
57798         return group;
57799     },
57800
57801     // @private
57802     prepareItems: function(items, applyDefaults) {
57803         items = [].concat(items);
57804         // Make sure defaults are applied and item is initialized
57805         var item, i, ln;
57806         for (i = 0, ln = items.length; i < ln; i++) {
57807             item = items[i];
57808             if (!(item instanceof Ext.draw.Sprite)) {
57809                 // Temporary, just take in configs...
57810                 item.surface = this;
57811                 items[i] = this.createItem(item);
57812             } else {
57813                 item.surface = this;
57814             }
57815         }
57816         return items;
57817     },
57818     
57819     /**
57820      * Changes the text in the sprite element. The sprite must be a `text` sprite.
57821      * This method can also be called from {@link Ext.draw.Sprite}.
57822      *
57823      * For example:
57824      *
57825      *      var spriteGroup = drawComponent.surface.setText(sprite, 'my new text');
57826      *      
57827      * @param {Object} sprite The Sprite to change the text.
57828      * @param {String} text The new text to be set.
57829      */
57830     setText: Ext.emptyFn,
57831     
57832     //@private Creates an item and appends it to the surface. Called
57833     //as an internal method when calling `add`.
57834     createItem: Ext.emptyFn,
57835
57836     /**
57837      * Retrieves the id of this component.
57838      * Will autogenerate an id if one has not already been set.
57839      */
57840     getId: function() {
57841         return this.id || (this.id = Ext.id(null, 'ext-surface-'));
57842     },
57843
57844     /**
57845      * Destroys the surface. This is done by removing all components from it and
57846      * also removing its reference to a DOM element.
57847      *
57848      * For example:
57849      *
57850      *      drawComponent.surface.destroy();
57851      */
57852     destroy: function() {
57853         delete this.domRef;
57854         this.removeAll();
57855     }
57856 });
57857 /**
57858  * @class Ext.draw.Component
57859  * @extends Ext.Component
57860  *
57861  * The Draw Component is a surface in which sprites can be rendered. The Draw Component
57862  * manages and holds a `Surface` instance: an interface that has
57863  * an SVG or VML implementation depending on the browser capabilities and where
57864  * Sprites can be appended.
57865  * {@img Ext.draw.Component/Ext.draw.Component.png Ext.draw.Component component}
57866  * One way to create a draw component is:
57867  * 
57868  *     var drawComponent = Ext.create('Ext.draw.Component', {
57869  *         viewBox: false,
57870  *         items: [{
57871  *             type: 'circle',
57872  *             fill: '#79BB3F',
57873  *             radius: 100,
57874  *             x: 100,
57875  *             y: 100
57876  *         }]
57877  *     });
57878  *   
57879  *     Ext.create('Ext.Window', {
57880  *         width: 215,
57881  *         height: 235,
57882  *         layout: 'fit',
57883  *         items: [drawComponent]
57884  *     }).show();
57885  * 
57886  * In this case we created a draw component and added a sprite to it.
57887  * The *type* of the sprite is *circle* so if you run this code you'll see a yellow-ish
57888  * circle in a Window. When setting `viewBox` to `false` we are responsible for setting the object's position and
57889  * dimensions accordingly. 
57890  * 
57891  * You can also add sprites by using the surface's add method:
57892  *    
57893  *     drawComponent.surface.add({
57894  *         type: 'circle',
57895  *         fill: '#79BB3F',
57896  *         radius: 100,
57897  *         x: 100,
57898  *         y: 100
57899  *     });
57900  *  
57901  * For more information on Sprites, the core elements added to a draw component's surface,
57902  * refer to the Ext.draw.Sprite documentation.
57903  */
57904 Ext.define('Ext.draw.Component', {
57905
57906     /* Begin Definitions */
57907
57908     alias: 'widget.draw',
57909
57910     extend: 'Ext.Component',
57911
57912     requires: [
57913         'Ext.draw.Surface',
57914         'Ext.layout.component.Draw'
57915     ],
57916
57917     /* End Definitions */
57918
57919     /**
57920      * @cfg {Array} enginePriority
57921      * Defines the priority order for which Surface implementation to use. The first
57922      * one supported by the current environment will be used.
57923      */
57924     enginePriority: ['Svg', 'Vml'],
57925
57926     baseCls: Ext.baseCSSPrefix + 'surface',
57927
57928     componentLayout: 'draw',
57929
57930     /**
57931      * @cfg {Boolean} viewBox
57932      * Turn on view box support which will scale and position items in the draw component to fit to the component while
57933      * maintaining aspect ratio. Note that this scaling can override other sizing settings on yor items. Defaults to true.
57934      */
57935     viewBox: true,
57936
57937     /**
57938      * @cfg {Boolean} autoSize
57939      * Turn on autoSize support which will set the bounding div's size to the natural size of the contents. Defaults to false.
57940      */
57941     autoSize: false,
57942     
57943     /**
57944      * @cfg {Array} gradients (optional) Define a set of gradients that can be used as `fill` property in sprites.
57945      * The gradients array is an array of objects with the following properties:
57946      *
57947      * <ul>
57948      * <li><strong>id</strong> - string - The unique name of the gradient.</li>
57949      * <li><strong>angle</strong> - number, optional - The angle of the gradient in degrees.</li>
57950      * <li><strong>stops</strong> - object - An object with numbers as keys (from 0 to 100) and style objects
57951      * as values</li>
57952      * </ul>
57953      * 
57954      
57955      For example:
57956      
57957      <pre><code>
57958         gradients: [{
57959             id: 'gradientId',
57960             angle: 45,
57961             stops: {
57962                 0: {
57963                     color: '#555'
57964                 },
57965                 100: {
57966                     color: '#ddd'
57967                 }
57968             }
57969         },  {
57970             id: 'gradientId2',
57971             angle: 0,
57972             stops: {
57973                 0: {
57974                     color: '#590'
57975                 },
57976                 20: {
57977                     color: '#599'
57978                 },
57979                 100: {
57980                     color: '#ddd'
57981                 }
57982             }
57983         }]
57984      </code></pre>
57985      
57986      Then the sprites can use `gradientId` and `gradientId2` by setting the fill attributes to those ids, for example:
57987      
57988      <pre><code>
57989         sprite.setAttributes({
57990             fill: 'url(#gradientId)'
57991         }, true);
57992      </code></pre>
57993      
57994      */
57995
57996     initComponent: function() {
57997         this.callParent(arguments);
57998
57999         this.addEvents(
58000             'mousedown',
58001             'mouseup',
58002             'mousemove',
58003             'mouseenter',
58004             'mouseleave',
58005             'click'
58006         );
58007     },
58008
58009     /**
58010      * @private
58011      *
58012      * Create the Surface on initial render
58013      */
58014     onRender: function() {
58015         var me = this,
58016             viewBox = me.viewBox,
58017             autoSize = me.autoSize,
58018             bbox, items, width, height, x, y;
58019         me.callParent(arguments);
58020
58021         me.createSurface();
58022
58023         items = me.surface.items;
58024
58025         if (viewBox || autoSize) {
58026             bbox = items.getBBox();
58027             width = bbox.width;
58028             height = bbox.height;
58029             x = bbox.x;
58030             y = bbox.y;
58031             if (me.viewBox) {
58032                 me.surface.setViewBox(x, y, width, height);
58033             }
58034             else {
58035                 // AutoSized
58036                 me.autoSizeSurface();
58037             }
58038         }
58039     },
58040
58041     //@private
58042     autoSizeSurface: function() {
58043         var me = this,
58044             items = me.surface.items,
58045             bbox = items.getBBox(),
58046             width = bbox.width,
58047             height = bbox.height;
58048         items.setAttributes({
58049             translate: {
58050                 x: -bbox.x,
58051                 //Opera has a slight offset in the y axis.
58052                 y: -bbox.y + (+Ext.isOpera)
58053             }
58054         }, true);
58055         if (me.rendered) {
58056             me.setSize(width, height);
58057         }
58058         else {
58059             me.surface.setSize(width, height);
58060         }
58061         me.el.setSize(width, height);
58062     },
58063
58064     /**
58065      * Create the Surface instance. Resolves the correct Surface implementation to
58066      * instantiate based on the 'enginePriority' config. Once the Surface instance is
58067      * created you can use the handle to that instance to add sprites. For example:
58068      *
58069      <pre><code>
58070         drawComponent.surface.add(sprite);
58071      </code></pre>
58072      */
58073     createSurface: function() {
58074         var surface = Ext.draw.Surface.create(Ext.apply({}, {
58075                 width: this.width,
58076                 height: this.height,
58077                 renderTo: this.el
58078             }, this.initialConfig));
58079         this.surface = surface;
58080
58081         function refire(eventName) {
58082             return function(e) {
58083                 this.fireEvent(eventName, e);
58084             };
58085         }
58086
58087         surface.on({
58088             scope: this,
58089             mouseup: refire('mouseup'),
58090             mousedown: refire('mousedown'),
58091             mousemove: refire('mousemove'),
58092             mouseenter: refire('mouseenter'),
58093             mouseleave: refire('mouseleave'),
58094             click: refire('click')
58095         });
58096     },
58097
58098
58099     /**
58100      * @private
58101      * 
58102      * Clean up the Surface instance on component destruction
58103      */
58104     onDestroy: function() {
58105         var surface = this.surface;
58106         if (surface) {
58107             surface.destroy();
58108         }
58109         this.callParent(arguments);
58110     }
58111
58112 });
58113
58114 /**
58115  * @class Ext.chart.LegendItem
58116  * @extends Ext.draw.CompositeSprite
58117  * A single item of a legend (marker plus label)
58118  * @constructor
58119  */
58120 Ext.define('Ext.chart.LegendItem', {
58121
58122     /* Begin Definitions */
58123
58124     extend: 'Ext.draw.CompositeSprite',
58125
58126     requires: ['Ext.chart.Shape'],
58127
58128     /* End Definitions */
58129
58130     // Position of the item, relative to the upper-left corner of the legend box
58131     x: 0,
58132     y: 0,
58133     zIndex: 500,
58134
58135     constructor: function(config) {
58136         this.callParent(arguments);
58137         this.createLegend(config);
58138     },
58139
58140     /**
58141      * Creates all the individual sprites for this legend item
58142      */
58143     createLegend: function(config) {
58144         var me = this,
58145             index = config.yFieldIndex,
58146             series = me.series,
58147             seriesType = series.type,
58148             idx = me.yFieldIndex,
58149             legend = me.legend,
58150             surface = me.surface,
58151             refX = legend.x + me.x,
58152             refY = legend.y + me.y,
58153             bbox, z = me.zIndex,
58154             markerConfig, label, mask,
58155             radius, toggle = false,
58156             seriesStyle = Ext.apply(series.seriesStyle, series.style);
58157
58158         function getSeriesProp(name) {
58159             var val = series[name];
58160             return (Ext.isArray(val) ? val[idx] : val);
58161         }
58162         
58163         label = me.add('label', surface.add({
58164             type: 'text',
58165             x: 20,
58166             y: 0,
58167             zIndex: z || 0,
58168             font: legend.labelFont,
58169             text: getSeriesProp('title') || getSeriesProp('yField')
58170         }));
58171
58172         // Line series - display as short line with optional marker in the middle
58173         if (seriesType === 'line' || seriesType === 'scatter') {
58174             if(seriesType === 'line') {
58175                 me.add('line', surface.add({
58176                     type: 'path',
58177                     path: 'M0.5,0.5L16.5,0.5',
58178                     zIndex: z,
58179                     "stroke-width": series.lineWidth,
58180                     "stroke-linejoin": "round",
58181                     "stroke-dasharray": series.dash,
58182                     stroke: seriesStyle.stroke || '#000',
58183                     style: {
58184                         cursor: 'pointer'
58185                     }
58186                 }));
58187             }
58188             if (series.showMarkers || seriesType === 'scatter') {
58189                 markerConfig = Ext.apply(series.markerStyle, series.markerConfig || {});
58190                 me.add('marker', Ext.chart.Shape[markerConfig.type](surface, {
58191                     fill: markerConfig.fill,
58192                     x: 8.5,
58193                     y: 0.5,
58194                     zIndex: z,
58195                     radius: markerConfig.radius || markerConfig.size,
58196                     style: {
58197                         cursor: 'pointer'
58198                     }
58199                 }));
58200             }
58201         }
58202         // All other series types - display as filled box
58203         else {
58204             me.add('box', surface.add({
58205                 type: 'rect',
58206                 zIndex: z,
58207                 x: 0,
58208                 y: 0,
58209                 width: 12,
58210                 height: 12,
58211                 fill: series.getLegendColor(index),
58212                 style: {
58213                     cursor: 'pointer'
58214                 }
58215             }));
58216         }
58217         
58218         me.setAttributes({
58219             hidden: false
58220         }, true);
58221         
58222         bbox = me.getBBox();
58223         
58224         mask = me.add('mask', surface.add({
58225             type: 'rect',
58226             x: bbox.x,
58227             y: bbox.y,
58228             width: bbox.width || 20,
58229             height: bbox.height || 20,
58230             zIndex: (z || 0) + 1000,
58231             fill: '#f00',
58232             opacity: 0,
58233             style: {
58234                 'cursor': 'pointer'
58235             }
58236         }));
58237
58238         //add toggle listener
58239         me.on('mouseover', function() {
58240             label.setStyle({
58241                 'font-weight': 'bold'
58242             });
58243             mask.setStyle({
58244                 'cursor': 'pointer'
58245             });
58246             series._index = index;
58247             series.highlightItem();
58248         }, me);
58249
58250         me.on('mouseout', function() {
58251             label.setStyle({
58252                 'font-weight': 'normal'
58253             });
58254             series._index = index;
58255             series.unHighlightItem();
58256         }, me);
58257         
58258         if (!series.visibleInLegend(index)) {
58259             toggle = true;
58260             label.setAttributes({
58261                opacity: 0.5
58262             }, true);
58263         }
58264
58265         me.on('mousedown', function() {
58266             if (!toggle) {
58267                 series.hideAll();
58268                 label.setAttributes({
58269                     opacity: 0.5
58270                 }, true);
58271             } else {
58272                 series.showAll();
58273                 label.setAttributes({
58274                     opacity: 1
58275                 }, true);
58276             }
58277             toggle = !toggle;
58278         }, me);
58279         me.updatePosition({x:0, y:0}); //Relative to 0,0 at first so that the bbox is calculated correctly
58280     },
58281
58282     /**
58283      * Update the positions of all this item's sprites to match the root position
58284      * of the legend box.
58285      * @param {Object} relativeTo (optional) If specified, this object's 'x' and 'y' values will be used
58286      *                 as the reference point for the relative positioning. Defaults to the Legend.
58287      */
58288     updatePosition: function(relativeTo) {
58289         var me = this,
58290             items = me.items,
58291             ln = items.length,
58292             i = 0,
58293             item;
58294         if (!relativeTo) {
58295             relativeTo = me.legend;
58296         }
58297         for (; i < ln; i++) {
58298             item = items[i];
58299             switch (item.type) {
58300                 case 'text':
58301                     item.setAttributes({
58302                         x: 20 + relativeTo.x + me.x,
58303                         y: relativeTo.y + me.y
58304                     }, true);
58305                     break;
58306                 case 'rect':
58307                     item.setAttributes({
58308                         translate: {
58309                             x: relativeTo.x + me.x,
58310                             y: relativeTo.y + me.y - 6
58311                         }
58312                     }, true);
58313                     break;
58314                 default:
58315                     item.setAttributes({
58316                         translate: {
58317                             x: relativeTo.x + me.x,
58318                             y: relativeTo.y + me.y
58319                         }
58320                     }, true);
58321             }
58322         }
58323     }
58324 });
58325 /**
58326  * @class Ext.chart.Legend
58327  *
58328  * Defines a legend for a chart's series.
58329  * The 'chart' member must be set prior to rendering.
58330  * The legend class displays a list of legend items each of them related with a
58331  * series being rendered. In order to render the legend item of the proper series
58332  * the series configuration object must have `showInSeries` set to true.
58333  *
58334  * The legend configuration object accepts a `position` as parameter.
58335  * The `position` parameter can be `left`, `right`
58336  * `top` or `bottom`. For example:
58337  *
58338  *     legend: {
58339  *         position: 'right'
58340  *     },
58341  * 
58342  * Full example:
58343     <pre><code>
58344     var store = Ext.create('Ext.data.JsonStore', {
58345         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
58346         data: [
58347             {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
58348             {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
58349             {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
58350             {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
58351             {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}                                                
58352         ]
58353     });
58354     
58355     Ext.create('Ext.chart.Chart', {
58356         renderTo: Ext.getBody(),
58357         width: 500,
58358         height: 300,
58359         animate: true,
58360         store: store,
58361         shadow: true,
58362         theme: 'Category1',
58363         legend: {
58364             position: 'top'
58365         },
58366          axes: [{
58367                 type: 'Numeric',
58368                 grid: true,
58369                 position: 'left',
58370                 fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
58371                 title: 'Sample Values',
58372                 grid: {
58373                     odd: {
58374                         opacity: 1,
58375                         fill: '#ddd',
58376                         stroke: '#bbb',
58377                         'stroke-width': 1
58378                     }
58379                 },
58380                 minimum: 0,
58381                 adjustMinimumByMajorUnit: 0
58382             }, {
58383                 type: 'Category',
58384                 position: 'bottom',
58385                 fields: ['name'],
58386                 title: 'Sample Metrics',
58387                 grid: true,
58388                 label: {
58389                     rotate: {
58390                         degrees: 315
58391                     }
58392                 }
58393         }],
58394         series: [{
58395             type: 'area',
58396             highlight: false,
58397             axis: 'left',
58398             xField: 'name',
58399             yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
58400             style: {
58401                 opacity: 0.93
58402             }
58403         }]
58404     });    
58405     </code></pre>    
58406  *
58407  * @constructor
58408  */
58409 Ext.define('Ext.chart.Legend', {
58410
58411     /* Begin Definitions */
58412
58413     requires: ['Ext.chart.LegendItem'],
58414
58415     /* End Definitions */
58416
58417     /**
58418      * @cfg {Boolean} visible
58419      * Whether or not the legend should be displayed.
58420      */
58421     visible: true,
58422
58423     /**
58424      * @cfg {String} position
58425      * The position of the legend in relation to the chart. One of: "top",
58426      * "bottom", "left", "right", or "float". If set to "float", then the legend
58427      * box will be positioned at the point denoted by the x and y parameters.
58428      */
58429     position: 'bottom',
58430
58431     /**
58432      * @cfg {Number} x
58433      * X-position of the legend box. Used directly if position is set to "float", otherwise 
58434      * it will be calculated dynamically.
58435      */
58436     x: 0,
58437
58438     /**
58439      * @cfg {Number} y
58440      * Y-position of the legend box. Used directly if position is set to "float", otherwise
58441      * it will be calculated dynamically.
58442      */
58443     y: 0,
58444
58445     /**
58446      * @cfg {String} labelFont
58447      * Font to be used for the legend labels, eg '12px Helvetica'
58448      */
58449     labelFont: '12px Helvetica, sans-serif',
58450
58451     /**
58452      * @cfg {String} boxStroke
58453      * Style of the stroke for the legend box
58454      */
58455     boxStroke: '#000',
58456
58457     /**
58458      * @cfg {String} boxStrokeWidth
58459      * Width of the stroke for the legend box
58460      */
58461     boxStrokeWidth: 1,
58462
58463     /**
58464      * @cfg {String} boxFill
58465      * Fill style for the legend box
58466      */
58467     boxFill: '#FFF',
58468
58469     /**
58470      * @cfg {Number} itemSpacing
58471      * Amount of space between legend items
58472      */
58473     itemSpacing: 10,
58474
58475     /**
58476      * @cfg {Number} padding
58477      * Amount of padding between the legend box's border and its items
58478      */
58479     padding: 5,
58480
58481     // @private
58482     width: 0,
58483     // @private
58484     height: 0,
58485
58486     /**
58487      * @cfg {Number} boxZIndex
58488      * Sets the z-index for the legend. Defaults to 100.
58489      */
58490     boxZIndex: 100,
58491
58492     constructor: function(config) {
58493         var me = this;
58494         if (config) {
58495             Ext.apply(me, config);
58496         }
58497         me.items = [];
58498         /**
58499          * Whether the legend box is oriented vertically, i.e. if it is on the left or right side or floating.
58500          * @type {Boolean}
58501          */
58502         me.isVertical = ("left|right|float".indexOf(me.position) !== -1);
58503         
58504         // cache these here since they may get modified later on
58505         me.origX = me.x;
58506         me.origY = me.y;
58507     },
58508
58509     /**
58510      * @private Create all the sprites for the legend
58511      */
58512     create: function() {
58513         var me = this;
58514         me.createItems();
58515         if (!me.created && me.isDisplayed()) {
58516             me.createBox();
58517             me.created = true;
58518
58519             // Listen for changes to series titles to trigger regeneration of the legend
58520             me.chart.series.each(function(series) {
58521                 series.on('titlechange', function() {
58522                     me.create();
58523                     me.updatePosition();
58524                 });
58525             });
58526         }
58527     },
58528
58529     /**
58530      * @private Determine whether the legend should be displayed. Looks at the legend's 'visible' config,
58531      * and also the 'showInLegend' config for each of the series.
58532      */
58533     isDisplayed: function() {
58534         return this.visible && this.chart.series.findIndex('showInLegend', true) !== -1;
58535     },
58536
58537     /**
58538      * @private Create the series markers and labels
58539      */
58540     createItems: function() {
58541         var me = this,
58542             chart = me.chart,
58543             surface = chart.surface,
58544             items = me.items,
58545             padding = me.padding,
58546             itemSpacing = me.itemSpacing,
58547             spacingOffset = 2,
58548             maxWidth = 0,
58549             maxHeight = 0,
58550             totalWidth = 0,
58551             totalHeight = 0,
58552             vertical = me.isVertical,
58553             math = Math,
58554             mfloor = math.floor,
58555             mmax = math.max,
58556             index = 0, 
58557             i = 0, 
58558             len = items ? items.length : 0,
58559             x, y, spacing, item, bbox, height, width;
58560
58561         //remove all legend items
58562         if (len) {
58563             for (; i < len; i++) {
58564                 items[i].destroy();
58565             }
58566         }
58567         //empty array
58568         items.length = [];
58569         // Create all the item labels, collecting their dimensions and positioning each one
58570         // properly in relation to the previous item
58571         chart.series.each(function(series, i) {
58572             if (series.showInLegend) {
58573                 Ext.each([].concat(series.yField), function(field, j) {
58574                     item = Ext.create('Ext.chart.LegendItem', {
58575                         legend: this,
58576                         series: series,
58577                         surface: chart.surface,
58578                         yFieldIndex: j
58579                     });
58580                     bbox = item.getBBox();
58581
58582                     //always measure from x=0, since not all markers go all the way to the left
58583                     width = bbox.width; 
58584                     height = bbox.height;
58585
58586                     if (i + j === 0) {
58587                         spacing = vertical ? padding + height / 2 : padding;
58588                     }
58589                     else {
58590                         spacing = itemSpacing / (vertical ? 2 : 1);
58591                     }
58592                     // Set the item's position relative to the legend box
58593                     item.x = mfloor(vertical ? padding : totalWidth + spacing);
58594                     item.y = mfloor(vertical ? totalHeight + spacing : padding + height / 2);
58595
58596                     // Collect cumulative dimensions
58597                     totalWidth += width + spacing;
58598                     totalHeight += height + spacing;
58599                     maxWidth = mmax(maxWidth, width);
58600                     maxHeight = mmax(maxHeight, height);
58601
58602                     items.push(item);
58603                 }, this);
58604             }
58605         }, me);
58606
58607         // Store the collected dimensions for later
58608         me.width = mfloor((vertical ? maxWidth : totalWidth) + padding * 2);
58609         if (vertical && items.length === 1) {
58610             spacingOffset = 1;
58611         }
58612         me.height = mfloor((vertical ? totalHeight - spacingOffset * spacing : maxHeight) + (padding * 2));
58613         me.itemHeight = maxHeight;
58614     },
58615
58616     /**
58617      * @private Get the bounds for the legend's outer box
58618      */
58619     getBBox: function() {
58620         var me = this;
58621         return {
58622             x: Math.round(me.x) - me.boxStrokeWidth / 2,
58623             y: Math.round(me.y) - me.boxStrokeWidth / 2,
58624             width: me.width,
58625             height: me.height
58626         };
58627     },
58628
58629     /**
58630      * @private Create the box around the legend items
58631      */
58632     createBox: function() {
58633         var me = this,
58634             box = me.boxSprite = me.chart.surface.add(Ext.apply({
58635                 type: 'rect',
58636                 stroke: me.boxStroke,
58637                 "stroke-width": me.boxStrokeWidth,
58638                 fill: me.boxFill,
58639                 zIndex: me.boxZIndex
58640             }, me.getBBox()));
58641         box.redraw();
58642     },
58643
58644     /**
58645      * @private Update the position of all the legend's sprites to match its current x/y values
58646      */
58647     updatePosition: function() {
58648         var me = this,
58649             x, y,
58650             legendWidth = me.width,
58651             legendHeight = me.height,
58652             padding = me.padding,
58653             chart = me.chart,
58654             chartBBox = chart.chartBBox,
58655             insets = chart.insetPadding,
58656             chartWidth = chartBBox.width - (insets * 2),
58657             chartHeight = chartBBox.height - (insets * 2),
58658             chartX = chartBBox.x + insets,
58659             chartY = chartBBox.y + insets,
58660             surface = chart.surface,
58661             mfloor = Math.floor;
58662         
58663         if (me.isDisplayed()) {
58664             // Find the position based on the dimensions
58665             switch(me.position) {
58666                 case "left":
58667                     x = insets;
58668                     y = mfloor(chartY + chartHeight / 2 - legendHeight / 2);
58669                     break;
58670                 case "right":
58671                     x = mfloor(surface.width - legendWidth) - insets;
58672                     y = mfloor(chartY + chartHeight / 2 - legendHeight / 2);
58673                     break;
58674                 case "top":
58675                     x = mfloor(chartX + chartWidth / 2 - legendWidth / 2);
58676                     y = insets;
58677                     break;
58678                 case "bottom":
58679                     x = mfloor(chartX + chartWidth / 2 - legendWidth / 2);
58680                     y = mfloor(surface.height - legendHeight) - insets;
58681                     break;
58682                 default:
58683                     x = mfloor(me.origX) + insets;
58684                     y = mfloor(me.origY) + insets;
58685             }
58686             me.x = x;
58687             me.y = y;
58688
58689             // Update the position of each item
58690             Ext.each(me.items, function(item) {
58691                 item.updatePosition();
58692             });
58693             // Update the position of the outer box
58694             me.boxSprite.setAttributes(me.getBBox(), true);
58695         }
58696     }
58697 });
58698 /**
58699  * @class Ext.chart.Chart
58700  * @extends Ext.draw.Component
58701  *
58702  * The Ext.chart package provides the capability to visualize data.
58703  * Each chart binds directly to an Ext.data.Store enabling automatic updates of the chart.
58704  * A chart configuration object has some overall styling options as well as an array of axes
58705  * and series. A chart instance example could look like:
58706  *
58707   <pre><code>
58708     Ext.create('Ext.chart.Chart', {
58709         renderTo: Ext.getBody(),
58710         width: 800,
58711         height: 600,
58712         animate: true,
58713         store: store1,
58714         shadow: true,
58715         theme: 'Category1',
58716         legend: {
58717             position: 'right'
58718         },
58719         axes: [ ...some axes options... ],
58720         series: [ ...some series options... ]
58721     });
58722   </code></pre>
58723  *
58724  * In this example we set the `width` and `height` of the chart, we decide whether our series are
58725  * animated or not and we select a store to be bound to the chart. We also turn on shadows for all series,
58726  * select a color theme `Category1` for coloring the series, set the legend to the right part of the chart and
58727  * then tell the chart to render itself in the body element of the document. For more information about the axes and
58728  * series configurations please check the documentation of each series (Line, Bar, Pie, etc).
58729  *
58730  * @xtype chart
58731  */
58732
58733 Ext.define('Ext.chart.Chart', {
58734
58735     /* Begin Definitions */
58736
58737     alias: 'widget.chart',
58738
58739     extend: 'Ext.draw.Component',
58740     
58741     mixins: {
58742         themeManager: 'Ext.chart.theme.Theme',
58743         mask: 'Ext.chart.Mask',
58744         navigation: 'Ext.chart.Navigation'
58745     },
58746
58747     requires: [
58748         'Ext.util.MixedCollection',
58749         'Ext.data.StoreManager',
58750         'Ext.chart.Legend',
58751         'Ext.util.DelayedTask'
58752     ],
58753
58754     /* End Definitions */
58755
58756     // @private
58757     viewBox: false,
58758
58759     /**
58760      * @cfg {String} theme (optional) The name of the theme to be used. A theme defines the colors and
58761      * other visual displays of tick marks on axis, text, title text, line colors, marker colors and styles, etc.
58762      * Possible theme values are 'Base', 'Green', 'Sky', 'Red', 'Purple', 'Blue', 'Yellow' and also six category themes
58763      * 'Category1' to 'Category6'. Default value is 'Base'.
58764      */
58765
58766     /**
58767      * @cfg {Boolean/Object} animate (optional) true for the default animation (easing: 'ease' and duration: 500)
58768      * or a standard animation config object to be used for default chart animations. Defaults to false.
58769      */
58770     animate: false,
58771
58772     /**
58773      * @cfg {Boolean/Object} legend (optional) true for the default legend display or a legend config object. Defaults to false.
58774      */
58775     legend: false,
58776
58777     /**
58778      * @cfg {integer} insetPadding (optional) Set the amount of inset padding in pixels for the chart. Defaults to 10.
58779      */
58780     insetPadding: 10,
58781
58782     /**
58783      * @cfg {Array} enginePriority
58784      * Defines the priority order for which Surface implementation to use. The first
58785      * one supported by the current environment will be used.
58786      */
58787     enginePriority: ['Svg', 'Vml'],
58788
58789     /**
58790      * @cfg {Object|Boolean} background (optional) Set the chart background. This can be a gradient object, image, or color.
58791      * Defaults to false for no background.
58792      *
58793      * For example, if `background` were to be a color we could set the object as
58794      *
58795      <pre><code>
58796         background: {
58797             //color string
58798             fill: '#ccc'
58799         }
58800      </code></pre>
58801
58802      You can specify an image by using:
58803
58804      <pre><code>
58805         background: {
58806             image: 'http://path.to.image/'
58807         }
58808      </code></pre>
58809
58810      Also you can specify a gradient by using the gradient object syntax:
58811
58812      <pre><code>
58813         background: {
58814             gradient: {
58815                 id: 'gradientId',
58816                 angle: 45,
58817                 stops: {
58818                     0: {
58819                         color: '#555'
58820                     }
58821                     100: {
58822                         color: '#ddd'
58823                     }
58824                 }
58825             }
58826         }
58827      </code></pre>
58828      */
58829     background: false,
58830
58831     /**
58832      * @cfg {Array} gradients (optional) Define a set of gradients that can be used as `fill` property in sprites.
58833      * The gradients array is an array of objects with the following properties:
58834      *
58835      * <ul>
58836      * <li><strong>id</strong> - string - The unique name of the gradient.</li>
58837      * <li><strong>angle</strong> - number, optional - The angle of the gradient in degrees.</li>
58838      * <li><strong>stops</strong> - object - An object with numbers as keys (from 0 to 100) and style objects
58839      * as values</li>
58840      * </ul>
58841      *
58842
58843      For example:
58844
58845      <pre><code>
58846         gradients: [{
58847             id: 'gradientId',
58848             angle: 45,
58849             stops: {
58850                 0: {
58851                     color: '#555'
58852                 },
58853                 100: {
58854                     color: '#ddd'
58855                 }
58856             }
58857         },  {
58858             id: 'gradientId2',
58859             angle: 0,
58860             stops: {
58861                 0: {
58862                     color: '#590'
58863                 },
58864                 20: {
58865                     color: '#599'
58866                 },
58867                 100: {
58868                     color: '#ddd'
58869                 }
58870             }
58871         }]
58872      </code></pre>
58873
58874      Then the sprites can use `gradientId` and `gradientId2` by setting the fill attributes to those ids, for example:
58875
58876      <pre><code>
58877         sprite.setAttributes({
58878             fill: 'url(#gradientId)'
58879         }, true);
58880      </code></pre>
58881
58882      */
58883
58884
58885     constructor: function(config) {
58886         var me = this,
58887             defaultAnim;
58888         me.initTheme(config.theme || me.theme);
58889         if (me.gradients) {
58890             Ext.apply(config, { gradients: me.gradients });
58891         }
58892         if (me.background) {
58893             Ext.apply(config, { background: me.background });
58894         }
58895         if (config.animate) {
58896             defaultAnim = {
58897                 easing: 'ease',
58898                 duration: 500
58899             };
58900             if (Ext.isObject(config.animate)) {
58901                 config.animate = Ext.applyIf(config.animate, defaultAnim);
58902             }
58903             else {
58904                 config.animate = defaultAnim;
58905             }
58906         }
58907         me.mixins.mask.constructor.call(me, config);
58908         me.mixins.navigation.constructor.call(me, config);
58909         me.callParent([config]);
58910     },
58911
58912     initComponent: function() {
58913         var me = this,
58914             axes,
58915             series;
58916         me.callParent();
58917         me.addEvents(
58918             'itemmousedown',
58919             'itemmouseup',
58920             'itemmouseover',
58921             'itemmouseout',
58922             'itemclick',
58923             'itemdoubleclick',
58924             'itemdragstart',
58925             'itemdrag',
58926             'itemdragend',
58927             /**
58928                  * @event beforerefresh
58929                  * Fires before a refresh to the chart data is called.  If the beforerefresh handler returns
58930                  * <tt>false</tt> the {@link #refresh} action will be cancelled.
58931                  * @param {Chart} this
58932                  */
58933             'beforerefresh',
58934             /**
58935                  * @event refresh
58936                  * Fires after the chart data has been refreshed.
58937                  * @param {Chart} this
58938                  */
58939             'refresh'
58940         );
58941         Ext.applyIf(me, {
58942             zoom: {
58943                 width: 1,
58944                 height: 1,
58945                 x: 0,
58946                 y: 0
58947             }
58948         });
58949         me.maxGutter = [0, 0];
58950         me.store = Ext.data.StoreManager.lookup(me.store);
58951         axes = me.axes;
58952         me.axes = Ext.create('Ext.util.MixedCollection', false, function(a) { return a.position; });
58953         if (axes) {
58954             me.axes.addAll(axes);
58955         }
58956         series = me.series;
58957         me.series = Ext.create('Ext.util.MixedCollection', false, function(a) { return a.seriesId || (a.seriesId = Ext.id(null, 'ext-chart-series-')); });
58958         if (series) {
58959             me.series.addAll(series);
58960         }
58961         if (me.legend !== false) {
58962             me.legend = Ext.create('Ext.chart.Legend', Ext.applyIf({chart:me}, me.legend));
58963         }
58964
58965         me.on({
58966             mousemove: me.onMouseMove,
58967             mouseleave: me.onMouseLeave,
58968             mousedown: me.onMouseDown,
58969             mouseup: me.onMouseUp,
58970             scope: me
58971         });
58972     },
58973
58974     // @private overrides the component method to set the correct dimensions to the chart.
58975     afterComponentLayout: function(width, height) {
58976         var me = this;
58977         if (Ext.isNumber(width) && Ext.isNumber(height)) {
58978             me.curWidth = width;
58979             me.curHeight = height;
58980             me.redraw(true);
58981         }
58982         this.callParent(arguments);
58983     },
58984
58985     /**
58986      * Redraw the chart. If animations are set this will animate the chart too.
58987      * @cfg {boolean} resize Optional flag which changes the default origin points of the chart for animations.
58988      */
58989     redraw: function(resize) {
58990         var me = this,
58991             chartBBox = me.chartBBox = {
58992                 x: 0,
58993                 y: 0,
58994                 height: me.curHeight,
58995                 width: me.curWidth
58996             },
58997             legend = me.legend;
58998         me.surface.setSize(chartBBox.width, chartBBox.height);
58999         // Instantiate Series and Axes
59000         me.series.each(me.initializeSeries, me);
59001         me.axes.each(me.initializeAxis, me);
59002         //process all views (aggregated data etc) on stores
59003         //before rendering.
59004         me.axes.each(function(axis) {
59005             axis.processView();
59006         });
59007         me.axes.each(function(axis) {
59008             axis.drawAxis(true);
59009         });
59010
59011         // Create legend if not already created
59012         if (legend !== false) {
59013             legend.create();
59014         }
59015
59016         // Place axes properly, including influence from each other
59017         me.alignAxes();
59018
59019         // Reposition legend based on new axis alignment
59020         if (me.legend !== false) {
59021             legend.updatePosition();
59022         }
59023
59024         // Find the max gutter
59025         me.getMaxGutter();
59026
59027         // Draw axes and series
59028         me.resizing = !!resize;
59029
59030         me.axes.each(me.drawAxis, me);
59031         me.series.each(me.drawCharts, me);
59032         me.resizing = false;
59033     },
59034
59035     // @private set the store after rendering the chart.
59036     afterRender: function() {
59037         var ref,
59038             me = this;
59039         this.callParent();
59040
59041         if (me.categoryNames) {
59042             me.setCategoryNames(me.categoryNames);
59043         }
59044
59045         if (me.tipRenderer) {
59046             ref = me.getFunctionRef(me.tipRenderer);
59047             me.setTipRenderer(ref.fn, ref.scope);
59048         }
59049         me.bindStore(me.store, true);
59050         me.refresh();
59051     },
59052
59053     // @private get x and y position of the mouse cursor.
59054     getEventXY: function(e) {
59055         var me = this,
59056             box = this.surface.getRegion(),
59057             pageXY = e.getXY(),
59058             x = pageXY[0] - box.left,
59059             y = pageXY[1] - box.top;
59060         return [x, y];
59061     },
59062
59063     // @private wrap the mouse down position to delegate the event to the series.
59064     onClick: function(e) {
59065         var me = this,
59066             position = me.getEventXY(e),
59067             item;
59068
59069         // Ask each series if it has an item corresponding to (not necessarily exactly
59070         // on top of) the current mouse coords. Fire itemclick event.
59071         me.series.each(function(series) {
59072             if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
59073                 if (series.getItemForPoint) {
59074                     item = series.getItemForPoint(position[0], position[1]);
59075                     if (item) {
59076                         series.fireEvent('itemclick', item);
59077                     }
59078                 }
59079             }
59080         }, me);
59081     },
59082
59083     // @private wrap the mouse down position to delegate the event to the series.
59084     onMouseDown: function(e) {
59085         var me = this,
59086             position = me.getEventXY(e),
59087             item;
59088
59089         if (me.mask) {
59090             me.mixins.mask.onMouseDown.call(me, e);
59091         }
59092         // Ask each series if it has an item corresponding to (not necessarily exactly
59093         // on top of) the current mouse coords. Fire mousedown event.
59094         me.series.each(function(series) {
59095             if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
59096                 if (series.getItemForPoint) {
59097                     item = series.getItemForPoint(position[0], position[1]);
59098                     if (item) {
59099                         series.fireEvent('itemmousedown', item);
59100                     }
59101                 }
59102             }
59103         }, me);
59104     },
59105
59106     // @private wrap the mouse up event to delegate it to the series.
59107     onMouseUp: function(e) {
59108         var me = this,
59109             position = me.getEventXY(e),
59110             item;
59111
59112         if (me.mask) {
59113             me.mixins.mask.onMouseUp.call(me, e);
59114         }
59115         // Ask each series if it has an item corresponding to (not necessarily exactly
59116         // on top of) the current mouse coords. Fire mousedown event.
59117         me.series.each(function(series) {
59118             if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
59119                 if (series.getItemForPoint) {
59120                     item = series.getItemForPoint(position[0], position[1]);
59121                     if (item) {
59122                         series.fireEvent('itemmouseup', item);
59123                     }
59124                 }
59125             }
59126         }, me);
59127     },
59128
59129     // @private wrap the mouse move event so it can be delegated to the series.
59130     onMouseMove: function(e) {
59131         var me = this,
59132             position = me.getEventXY(e),
59133             item, last, storeItem, storeField;
59134
59135         if (me.mask) {
59136             me.mixins.mask.onMouseMove.call(me, e);
59137         }
59138         // Ask each series if it has an item corresponding to (not necessarily exactly
59139         // on top of) the current mouse coords. Fire itemmouseover/out events.
59140         me.series.each(function(series) {
59141             if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
59142                 if (series.getItemForPoint) {
59143                     item = series.getItemForPoint(position[0], position[1]);
59144                     last = series._lastItemForPoint;
59145                     storeItem = series._lastStoreItem;
59146                     storeField = series._lastStoreField;
59147
59148
59149                     if (item !== last || item && (item.storeItem != storeItem || item.storeField != storeField)) {
59150                         if (last) {
59151                             series.fireEvent('itemmouseout', last);
59152                             delete series._lastItemForPoint;
59153                             delete series._lastStoreField;
59154                             delete series._lastStoreItem;
59155                         }
59156                         if (item) {
59157                             series.fireEvent('itemmouseover', item);
59158                             series._lastItemForPoint = item;
59159                             series._lastStoreItem = item.storeItem;
59160                             series._lastStoreField = item.storeField;
59161                         }
59162                     }
59163                 }
59164             } else {
59165                 last = series._lastItemForPoint;
59166                 if (last) {
59167                     series.fireEvent('itemmouseout', last);
59168                     delete series._lastItemForPoint;
59169                     delete series._lastStoreField;
59170                     delete series._lastStoreItem;
59171                 }
59172             }
59173         }, me);
59174     },
59175
59176     // @private handle mouse leave event.
59177     onMouseLeave: function(e) {
59178         var me = this;
59179         if (me.mask) {
59180             me.mixins.mask.onMouseLeave.call(me, e);
59181         }
59182         me.series.each(function(series) {
59183             delete series._lastItemForPoint;
59184         });
59185     },
59186
59187     // @private buffered refresh for when we update the store
59188     delayRefresh: function() {
59189         var me = this;
59190         if (!me.refreshTask) {
59191             me.refreshTask = Ext.create('Ext.util.DelayedTask', me.refresh, me);
59192         }
59193         me.refreshTask.delay(me.refreshBuffer);
59194     },
59195
59196     // @private
59197     refresh: function() {
59198         var me = this;
59199         if (me.rendered && me.curWidth != undefined && me.curHeight != undefined) {
59200             if (me.fireEvent('beforerefresh', me) !== false) {
59201                 me.redraw();
59202                 me.fireEvent('refresh', me);
59203             }
59204         }
59205     },
59206
59207     /**
59208      * Changes the data store bound to this chart and refreshes it.
59209      * @param {Store} store The store to bind to this chart
59210      */
59211     bindStore: function(store, initial) {
59212         var me = this;
59213         if (!initial && me.store) {
59214             if (store !== me.store && me.store.autoDestroy) {
59215                 me.store.destroy();
59216             }
59217             else {
59218                 me.store.un('datachanged', me.refresh, me);
59219                 me.store.un('add', me.delayRefresh, me);
59220                 me.store.un('remove', me.delayRefresh, me);
59221                 me.store.un('update', me.delayRefresh, me);
59222                 me.store.un('clear', me.refresh, me);
59223             }
59224         }
59225         if (store) {
59226             store = Ext.data.StoreManager.lookup(store);
59227             store.on({
59228                 scope: me,
59229                 datachanged: me.refresh,
59230                 add: me.delayRefresh,
59231                 remove: me.delayRefresh,
59232                 update: me.delayRefresh,
59233                 clear: me.refresh
59234             });
59235         }
59236         me.store = store;
59237         if (store && !initial) {
59238             me.refresh();
59239         }
59240     },
59241
59242     // @private Create Axis
59243     initializeAxis: function(axis) {
59244         var me = this,
59245             chartBBox = me.chartBBox,
59246             w = chartBBox.width,
59247             h = chartBBox.height,
59248             x = chartBBox.x,
59249             y = chartBBox.y,
59250             themeAttrs = me.themeAttrs,
59251             config = {
59252                 chart: me
59253             };
59254         if (themeAttrs) {
59255             config.axisStyle = Ext.apply({}, themeAttrs.axis);
59256             config.axisLabelLeftStyle = Ext.apply({}, themeAttrs.axisLabelLeft);
59257             config.axisLabelRightStyle = Ext.apply({}, themeAttrs.axisLabelRight);
59258             config.axisLabelTopStyle = Ext.apply({}, themeAttrs.axisLabelTop);
59259             config.axisLabelBottomStyle = Ext.apply({}, themeAttrs.axisLabelBottom);
59260             config.axisTitleLeftStyle = Ext.apply({}, themeAttrs.axisTitleLeft);
59261             config.axisTitleRightStyle = Ext.apply({}, themeAttrs.axisTitleRight);
59262             config.axisTitleTopStyle = Ext.apply({}, themeAttrs.axisTitleTop);
59263             config.axisTitleBottomStyle = Ext.apply({}, themeAttrs.axisTitleBottom);
59264         }
59265         switch (axis.position) {
59266             case 'top':
59267                 Ext.apply(config, {
59268                     length: w,
59269                     width: h,
59270                     x: x,
59271                     y: y
59272                 });
59273             break;
59274             case 'bottom':
59275                 Ext.apply(config, {
59276                     length: w,
59277                     width: h,
59278                     x: x,
59279                     y: h
59280                 });
59281             break;
59282             case 'left':
59283                 Ext.apply(config, {
59284                     length: h,
59285                     width: w,
59286                     x: x,
59287                     y: h
59288                 });
59289             break;
59290             case 'right':
59291                 Ext.apply(config, {
59292                     length: h,
59293                     width: w,
59294                     x: w,
59295                     y: h
59296                 });
59297             break;
59298         }
59299         if (!axis.chart) {
59300             Ext.apply(config, axis);
59301             axis = me.axes.replace(Ext.createByAlias('axis.' + axis.type.toLowerCase(), config));
59302         }
59303         else {
59304             Ext.apply(axis, config);
59305         }
59306     },
59307
59308
59309     /**
59310      * @private Adjust the dimensions and positions of each axis and the chart body area after accounting
59311      * for the space taken up on each side by the axes and legend.
59312      */
59313     alignAxes: function() {
59314         var me = this,
59315             axes = me.axes,
59316             legend = me.legend,
59317             edges = ['top', 'right', 'bottom', 'left'],
59318             chartBBox,
59319             insetPadding = me.insetPadding,
59320             insets = {
59321                 top: insetPadding,
59322                 right: insetPadding,
59323                 bottom: insetPadding,
59324                 left: insetPadding
59325             };
59326
59327         function getAxis(edge) {
59328             var i = axes.findIndex('position', edge);
59329             return (i < 0) ? null : axes.getAt(i);
59330         }
59331
59332         // Find the space needed by axes and legend as a positive inset from each edge
59333         Ext.each(edges, function(edge) {
59334             var isVertical = (edge === 'left' || edge === 'right'),
59335                 axis = getAxis(edge),
59336                 bbox;
59337
59338             // Add legend size if it's on this edge
59339             if (legend !== false) {
59340                 if (legend.position === edge) {
59341                     bbox = legend.getBBox();
59342                     insets[edge] += (isVertical ? bbox.width : bbox.height) + insets[edge];
59343                 }
59344             }
59345
59346             // Add axis size if there's one on this edge only if it has been
59347             //drawn before.
59348             if (axis && axis.bbox) {
59349                 bbox = axis.bbox;
59350                 insets[edge] += (isVertical ? bbox.width : bbox.height);
59351             }
59352         });
59353         // Build the chart bbox based on the collected inset values
59354         chartBBox = {
59355             x: insets.left,
59356             y: insets.top,
59357             width: me.curWidth - insets.left - insets.right,
59358             height: me.curHeight - insets.top - insets.bottom
59359         };
59360         me.chartBBox = chartBBox;
59361
59362         // Go back through each axis and set its length and position based on the
59363         // corresponding edge of the chartBBox
59364         axes.each(function(axis) {
59365             var pos = axis.position,
59366                 isVertical = (pos === 'left' || pos === 'right');
59367
59368             axis.x = (pos === 'right' ? chartBBox.x + chartBBox.width : chartBBox.x);
59369             axis.y = (pos === 'top' ? chartBBox.y : chartBBox.y + chartBBox.height);
59370             axis.width = (isVertical ? chartBBox.width : chartBBox.height);
59371             axis.length = (isVertical ? chartBBox.height : chartBBox.width);
59372         });
59373     },
59374
59375     // @private initialize the series.
59376     initializeSeries: function(series, idx) {
59377         var me = this,
59378             themeAttrs = me.themeAttrs,
59379             seriesObj, markerObj, seriesThemes, st,
59380             markerThemes, colorArrayStyle = [],
59381             i = 0, l,
59382             config = {
59383                 chart: me,
59384                 seriesId: series.seriesId
59385             };
59386         if (themeAttrs) {
59387             seriesThemes = themeAttrs.seriesThemes;
59388             markerThemes = themeAttrs.markerThemes;
59389             seriesObj = Ext.apply({}, themeAttrs.series);
59390             markerObj = Ext.apply({}, themeAttrs.marker);
59391             config.seriesStyle = Ext.apply(seriesObj, seriesThemes[idx % seriesThemes.length]);
59392             config.seriesLabelStyle = Ext.apply({}, themeAttrs.seriesLabel);
59393             config.markerStyle = Ext.apply(markerObj, markerThemes[idx % markerThemes.length]);
59394             if (themeAttrs.colors) {
59395                 config.colorArrayStyle = themeAttrs.colors;
59396             } else {
59397                 colorArrayStyle = [];
59398                 for (l = seriesThemes.length; i < l; i++) {
59399                     st = seriesThemes[i];
59400                     if (st.fill || st.stroke) {
59401                         colorArrayStyle.push(st.fill || st.stroke);
59402                     }
59403                 }
59404                 if (colorArrayStyle.length) {
59405                     config.colorArrayStyle = colorArrayStyle;
59406                 }
59407             }
59408             config.seriesIdx = idx;
59409         }
59410         if (series instanceof Ext.chart.series.Series) {
59411             Ext.apply(series, config);
59412         } else {
59413             Ext.applyIf(config, series);
59414             series = me.series.replace(Ext.createByAlias('series.' + series.type.toLowerCase(), config));
59415         }
59416         if (series.initialize) {
59417             series.initialize();
59418         }
59419     },
59420
59421     // @private
59422     getMaxGutter: function() {
59423         var me = this,
59424             maxGutter = [0, 0];
59425         me.series.each(function(s) {
59426             var gutter = s.getGutters && s.getGutters() || [0, 0];
59427             maxGutter[0] = Math.max(maxGutter[0], gutter[0]);
59428             maxGutter[1] = Math.max(maxGutter[1], gutter[1]);
59429         });
59430         me.maxGutter = maxGutter;
59431     },
59432
59433     // @private draw axis.
59434     drawAxis: function(axis) {
59435         axis.drawAxis();
59436     },
59437
59438     // @private draw series.
59439     drawCharts: function(series) {
59440         series.triggerafterrender = false;
59441         series.drawSeries();
59442         if (!this.animate) {
59443             series.fireEvent('afterrender');
59444         }
59445     },
59446
59447     // @private remove gently.
59448     destroy: function() {
59449         this.surface.destroy();
59450         this.bindStore(null);
59451         this.callParent(arguments);
59452     }
59453 });
59454
59455 /**
59456  * @class Ext.chart.Highlight
59457  * @ignore
59458  */
59459 Ext.define('Ext.chart.Highlight', {
59460
59461     /* Begin Definitions */
59462
59463     requires: ['Ext.fx.Anim'],
59464
59465     /* End Definitions */
59466
59467     /**
59468      * Highlight the given series item.
59469      * @param {Boolean|Object} Default's false. Can also be an object width style properties (i.e fill, stroke, radius) 
59470      * or just use default styles per series by setting highlight = true.
59471      */
59472     highlight: false,
59473
59474     highlightCfg : null,
59475
59476     constructor: function(config) {
59477         if (config.highlight) {
59478             if (config.highlight !== true) { //is an object
59479                 this.highlightCfg = Ext.apply({}, config.highlight);
59480             }
59481             else {
59482                 this.highlightCfg = {
59483                     fill: '#fdd',
59484                     radius: 20,
59485                     lineWidth: 5,
59486                     stroke: '#f55'
59487                 };
59488             }
59489         }
59490     },
59491
59492     /**
59493      * Highlight the given series item.
59494      * @param {Object} item Info about the item; same format as returned by #getItemForPoint.
59495      */
59496     highlightItem: function(item) {
59497         if (!item) {
59498             return;
59499         }
59500         
59501         var me = this,
59502             sprite = item.sprite,
59503             opts = me.highlightCfg,
59504             surface = me.chart.surface,
59505             animate = me.chart.animate,
59506             p,
59507             from,
59508             to,
59509             pi;
59510
59511         if (!me.highlight || !sprite || sprite._highlighted) {
59512             return;
59513         }
59514         if (sprite._anim) {
59515             sprite._anim.paused = true;
59516         }
59517         sprite._highlighted = true;
59518         if (!sprite._defaults) {
59519             sprite._defaults = Ext.apply(sprite._defaults || {},
59520             sprite.attr);
59521             from = {};
59522             to = {};
59523             for (p in opts) {
59524                 if (! (p in sprite._defaults)) {
59525                     sprite._defaults[p] = surface.availableAttrs[p];
59526                 }
59527                 from[p] = sprite._defaults[p];
59528                 to[p] = opts[p];
59529                 if (Ext.isObject(opts[p])) {
59530                     from[p] = {};
59531                     to[p] = {};
59532                     Ext.apply(sprite._defaults[p], sprite.attr[p]);
59533                     Ext.apply(from[p], sprite._defaults[p]);
59534                     for (pi in sprite._defaults[p]) {
59535                         if (! (pi in opts[p])) {
59536                             to[p][pi] = from[p][pi];
59537                         } else {
59538                             to[p][pi] = opts[p][pi];
59539                         }
59540                     }
59541                     for (pi in opts[p]) {
59542                         if (! (pi in to[p])) {
59543                             to[p][pi] = opts[p][pi];
59544                         }
59545                     }
59546                 }
59547             }
59548             sprite._from = from;
59549             sprite._to = to;
59550         }
59551         if (animate) {
59552             sprite._anim = Ext.create('Ext.fx.Anim', {
59553                 target: sprite,
59554                 from: sprite._from,
59555                 to: sprite._to,
59556                 duration: 150
59557             });
59558         } else {
59559             sprite.setAttributes(sprite._to, true);
59560         }
59561     },
59562
59563     /**
59564      * Un-highlight any existing highlights
59565      */
59566     unHighlightItem: function() {
59567         if (!this.highlight || !this.items) {
59568             return;
59569         }
59570
59571         var me = this,
59572             items = me.items,
59573             len = items.length,
59574             opts = me.highlightCfg,
59575             animate = me.chart.animate,
59576             i = 0,
59577             obj,
59578             p,
59579             sprite;
59580
59581         for (; i < len; i++) {
59582             if (!items[i]) {
59583                 continue;
59584             }
59585             sprite = items[i].sprite;
59586             if (sprite && sprite._highlighted) {
59587                 if (sprite._anim) {
59588                     sprite._anim.paused = true;
59589                 }
59590                 obj = {};
59591                 for (p in opts) {
59592                     if (Ext.isObject(sprite._defaults[p])) {
59593                         obj[p] = {};
59594                         Ext.apply(obj[p], sprite._defaults[p]);
59595                     }
59596                     else {
59597                         obj[p] = sprite._defaults[p];
59598                     }
59599                 }
59600                 if (animate) {
59601                     sprite._anim = Ext.create('Ext.fx.Anim', {
59602                         target: sprite,
59603                         to: obj,
59604                         duration: 150
59605                     });
59606                 }
59607                 else {
59608                     sprite.setAttributes(obj, true);
59609                 }
59610                 delete sprite._highlighted;
59611                 //delete sprite._defaults;
59612             }
59613         }
59614     },
59615
59616     cleanHighlights: function() {
59617         if (!this.highlight) {
59618             return;
59619         }
59620
59621         var group = this.group,
59622             markerGroup = this.markerGroup,
59623             i = 0,
59624             l;
59625         for (l = group.getCount(); i < l; i++) {
59626             delete group.getAt(i)._defaults;
59627         }
59628         if (markerGroup) {
59629             for (l = markerGroup.getCount(); i < l; i++) {
59630                 delete markerGroup.getAt(i)._defaults;
59631             }
59632         }
59633     }
59634 });
59635 /**
59636  * @class Ext.chart.Label
59637  *
59638  * Labels is a mixin whose methods are appended onto the Series class. Labels is an interface with methods implemented
59639  * in each of the Series (Pie, Bar, etc) for label creation and label placement.
59640  *
59641  * The methods implemented by the Series are:
59642  *  
59643  * - **`onCreateLabel(storeItem, item, i, display)`** Called each time a new label is created.
59644  *   The arguments of the method are:
59645  *   - *`storeItem`* The element of the store that is related to the label sprite.
59646  *   - *`item`* The item related to the label sprite. An item is an object containing the position of the shape
59647  *     used to describe the visualization and also pointing to the actual shape (circle, rectangle, path, etc).
59648  *   - *`i`* The index of the element created (i.e the first created label, second created label, etc)
59649  *   - *`display`* The display type. May be <b>false</b> if the label is hidden
59650  *
59651  *  - **`onPlaceLabel(label, storeItem, item, i, display, animate)`** Called for updating the position of the label.
59652  *    The arguments of the method are:
59653  *    - *`label`* The sprite label.</li>
59654  *    - *`storeItem`* The element of the store that is related to the label sprite</li>
59655  *    - *`item`* The item related to the label sprite. An item is an object containing the position of the shape
59656  *      used to describe the visualization and also pointing to the actual shape (circle, rectangle, path, etc).
59657  *    - *`i`* The index of the element to be updated (i.e. whether it is the first, second, third from the labelGroup)
59658  *    - *`display`* The display type. May be <b>false</b> if the label is hidden.
59659  *    - *`animate`* A boolean value to set or unset animations for the labels.
59660  */
59661 Ext.define('Ext.chart.Label', {
59662
59663     /* Begin Definitions */
59664
59665     requires: ['Ext.draw.Color'],
59666     
59667     /* End Definitions */
59668
59669     /**
59670      * @cfg {String} display
59671      * Specifies the presence and position of labels for each pie slice. Either "rotate", "middle", "insideStart",
59672      * "insideEnd", "outside", "over", "under", or "none" to prevent label rendering.
59673      * Default value: 'none'.
59674      */
59675
59676     /**
59677      * @cfg {String} color
59678      * The color of the label text.
59679      * Default value: '#000' (black).
59680      */
59681
59682     /**
59683      * @cfg {String} field
59684      * The name of the field to be displayed in the label.
59685      * Default value: 'name'.
59686      */
59687
59688     /**
59689      * @cfg {Number} minMargin
59690      * Specifies the minimum distance from a label to the origin of the visualization.
59691      * This parameter is useful when using PieSeries width variable pie slice lengths.
59692      * Default value: 50.
59693      */
59694
59695     /**
59696      * @cfg {String} font
59697      * The font used for the labels.
59698      * Defautl value: "11px Helvetica, sans-serif".
59699      */
59700
59701     /**
59702      * @cfg {String} orientation
59703      * Either "horizontal" or "vertical".
59704      * Dafault value: "horizontal".
59705      */
59706
59707     /**
59708      * @cfg {Function} renderer
59709      * Optional function for formatting the label into a displayable value.
59710      * Default value: function(v) { return v; }
59711      * @param v
59712      */
59713
59714     //@private a regex to parse url type colors.
59715     colorStringRe: /url\s*\(\s*#([^\/)]+)\s*\)/,
59716     
59717     //@private the mixin constructor. Used internally by Series.
59718     constructor: function(config) {
59719         var me = this;
59720         me.label = Ext.applyIf(me.label || {},
59721         {
59722             display: "none",
59723             color: "#000",
59724             field: "name",
59725             minMargin: 50,
59726             font: "11px Helvetica, sans-serif",
59727             orientation: "horizontal",
59728             renderer: function(v) {
59729                 return v;
59730             }
59731         });
59732
59733         if (me.label.display !== 'none') {
59734             me.labelsGroup = me.chart.surface.getGroup(me.seriesId + '-labels');
59735         }
59736     },
59737
59738     //@private a method to render all labels in the labelGroup
59739     renderLabels: function() {
59740         var me = this,
59741             chart = me.chart,
59742             gradients = chart.gradients,
59743             gradient,
59744             items = me.items,
59745             animate = chart.animate,
59746             config = me.label,
59747             display = config.display,
59748             color = config.color,
59749             field = [].concat(config.field),
59750             group = me.labelsGroup,
59751             store = me.chart.store,
59752             len = store.getCount(),
59753             ratio = items.length / len,
59754             i, count, j, 
59755             k, gradientsCount = (gradients || 0) && gradients.length,
59756             colorStopTotal, colorStopIndex, colorStop,
59757             item, label, storeItem,
59758             sprite, spriteColor, spriteBrightness, labelColor,
59759             Color = Ext.draw.Color,
59760             colorString;
59761
59762         if (display == 'none') {
59763             return;
59764         }
59765
59766         for (i = 0, count = 0; i < len; i++) {
59767             for (j = 0; j < ratio; j++) {
59768                 item = items[count];
59769                 label = group.getAt(count);
59770                 storeItem = store.getAt(i);
59771
59772                 if (!item && label) {
59773                     label.hide(true);
59774                 }
59775
59776                 if (item && field[j]) {
59777                     if (!label) {
59778                         label = me.onCreateLabel(storeItem, item, i, display, j, count);
59779                     }
59780                     me.onPlaceLabel(label, storeItem, item, i, display, animate, j, count);
59781
59782                     //set contrast
59783                     if (config.contrast && item.sprite) {
59784                         sprite = item.sprite;
59785                         colorString = sprite._to && sprite._to.fill || sprite.attr.fill;
59786                         spriteColor = Color.fromString(colorString);
59787                         //color wasn't parsed property maybe because it's a gradient id
59788                         if (colorString && !spriteColor) {
59789                             colorString = colorString.match(me.colorStringRe)[1];
59790                             for (k = 0; k < gradientsCount; k++) {
59791                                 gradient = gradients[k];
59792                                 if (gradient.id == colorString) {
59793                                     //avg color stops
59794                                     colorStop = 0; colorStopTotal = 0;
59795                                     for (colorStopIndex in gradient.stops) {
59796                                         colorStop++;
59797                                         colorStopTotal += Color.fromString(gradient.stops[colorStopIndex].color).getGrayscale();
59798                                     }
59799                                     spriteBrightness = (colorStopTotal / colorStop) / 255;
59800                                     break;
59801                                 }
59802                             }
59803                         }
59804                         else {
59805                             spriteBrightness = spriteColor.getGrayscale() / 255;
59806                         }
59807                         labelColor = Color.fromString(label.attr.color || label.attr.fill).getHSL();
59808                         
59809                         labelColor[2] = spriteBrightness > 0.5? 0.2 : 0.8;
59810                         label.setAttributes({
59811                             fill: String(Color.fromHSL.apply({}, labelColor))
59812                         }, true);
59813                     }
59814                 }
59815                 count++;
59816             }
59817         }
59818         me.hideLabels(count);
59819     },
59820
59821     //@private a method to hide labels.
59822     hideLabels: function(index) {
59823         var labelsGroup = this.labelsGroup, len;
59824         if (labelsGroup) {
59825             len = labelsGroup.getCount();
59826             while (len-->index) {
59827                 labelsGroup.getAt(len).hide(true);
59828             }
59829         }
59830     }
59831 });
59832 Ext.define('Ext.chart.MaskLayer', {
59833     extend: 'Ext.Component',
59834     
59835     constructor: function(config) {
59836         config = Ext.apply(config || {}, {
59837             style: 'position:absolute;background-color:#888;cursor:move;opacity:0.6;border:1px solid #222;'
59838         });
59839         this.callParent([config]);    
59840     },
59841     
59842     initComponent: function() {
59843         var me = this;
59844         me.callParent(arguments);
59845         me.addEvents(
59846             'mousedown',
59847             'mouseup',
59848             'mousemove',
59849             'mouseenter',
59850             'mouseleave'
59851         );
59852     },
59853
59854     initDraggable: function() {
59855         this.callParent(arguments);
59856         this.dd.onStart = function (e) {
59857             var me = this,
59858                 comp = me.comp;
59859     
59860             // Cache the start [X, Y] array
59861             this.startPosition = comp.getPosition(true);
59862     
59863             // If client Component has a ghost method to show a lightweight version of itself
59864             // then use that as a drag proxy unless configured to liveDrag.
59865             if (comp.ghost && !comp.liveDrag) {
59866                  me.proxy = comp.ghost();
59867                  me.dragTarget = me.proxy.header.el;
59868             }
59869     
59870             // Set the constrainTo Region before we start dragging.
59871             if (me.constrain || me.constrainDelegate) {
59872                 me.constrainTo = me.calculateConstrainRegion();
59873             }
59874         };
59875     }
59876 });
59877 /**
59878  * @class Ext.chart.TipSurface
59879  * @ignore
59880  */
59881 Ext.define('Ext.chart.TipSurface', {
59882
59883     /* Begin Definitions */
59884
59885     extend: 'Ext.draw.Component',
59886
59887     /* End Definitions */
59888
59889     spriteArray: false,
59890     renderFirst: true,
59891
59892     constructor: function(config) {
59893         this.callParent([config]);
59894         if (config.sprites) {
59895             this.spriteArray = [].concat(config.sprites);
59896             delete config.sprites;
59897         }
59898     },
59899
59900     onRender: function() {
59901         var me = this,
59902             i = 0,
59903             l = 0,
59904             sp,
59905             sprites;
59906             this.callParent(arguments);
59907         sprites = me.spriteArray;
59908         if (me.renderFirst && sprites) {
59909             me.renderFirst = false;
59910             for (l = sprites.length; i < l; i++) {
59911                 sp = me.surface.add(sprites[i]);
59912                 sp.setAttributes({
59913                     hidden: false
59914                 },
59915                 true);
59916             }
59917         }
59918     }
59919 });
59920
59921 /**
59922  * @class Ext.chart.Tip
59923  * @ignore
59924  */
59925 Ext.define('Ext.chart.Tip', {
59926
59927     /* Begin Definitions */
59928
59929     requires: ['Ext.tip.ToolTip', 'Ext.chart.TipSurface'],
59930
59931     /* End Definitions */
59932
59933     constructor: function(config) {
59934         var me = this,
59935             surface,
59936             sprites,
59937             tipSurface;
59938         if (config.tips) {
59939             me.tipTimeout = null;
59940             me.tipConfig = Ext.apply({}, config.tips, {
59941                 renderer: Ext.emptyFn,
59942                 constrainPosition: false
59943             });
59944             me.tooltip = Ext.create('Ext.tip.ToolTip', me.tipConfig);
59945             Ext.getBody().on('mousemove', me.tooltip.onMouseMove, me.tooltip);
59946             if (me.tipConfig.surface) {
59947                 //initialize a surface
59948                 surface = me.tipConfig.surface;
59949                 sprites = surface.sprites;
59950                 tipSurface = Ext.create('Ext.chart.TipSurface', {
59951                     id: 'tipSurfaceComponent',
59952                     sprites: sprites
59953                 });
59954                 if (surface.width && surface.height) {
59955                     tipSurface.setSize(surface.width, surface.height);
59956                 }
59957                 me.tooltip.add(tipSurface);
59958                 me.spriteTip = tipSurface;
59959             }
59960         }
59961     },
59962
59963     showTip: function(item) {
59964         var me = this;
59965         if (!me.tooltip) {
59966             return;
59967         }
59968         clearTimeout(me.tipTimeout);
59969         var tooltip = me.tooltip,
59970             spriteTip = me.spriteTip,
59971             tipConfig = me.tipConfig,
59972             trackMouse = tooltip.trackMouse,
59973             sprite, surface, surfaceExt, pos, x, y;
59974         if (!trackMouse) {
59975             tooltip.trackMouse = true;
59976             sprite = item.sprite;
59977             surface = sprite.surface;
59978             surfaceExt = Ext.get(surface.getId());
59979             if (surfaceExt) {
59980                 pos = surfaceExt.getXY();
59981                 x = pos[0] + (sprite.attr.x || 0) + (sprite.attr.translation && sprite.attr.translation.x || 0);
59982                 y = pos[1] + (sprite.attr.y || 0) + (sprite.attr.translation && sprite.attr.translation.y || 0);
59983                 tooltip.targetXY = [x, y];
59984             }
59985         }
59986         if (spriteTip) {
59987             tipConfig.renderer.call(tooltip, item.storeItem, item, spriteTip.surface);
59988         } else {
59989             tipConfig.renderer.call(tooltip, item.storeItem, item);
59990         }
59991         tooltip.show();
59992         tooltip.trackMouse = trackMouse;
59993     },
59994
59995     hideTip: function(item) {
59996         var tooltip = this.tooltip;
59997         if (!tooltip) {
59998             return;
59999         }
60000         clearTimeout(this.tipTimeout);
60001         this.tipTimeout = setTimeout(function() {
60002             tooltip.hide();
60003         }, 0);
60004     }
60005 });
60006 /**
60007  * @class Ext.chart.axis.Abstract
60008  * @ignore
60009  */
60010 Ext.define('Ext.chart.axis.Abstract', {
60011
60012     /* Begin Definitions */
60013
60014     requires: ['Ext.chart.Chart'],
60015
60016     /* End Definitions */
60017
60018     constructor: function(config) {
60019         config = config || {};
60020
60021         var me = this,
60022             pos = config.position || 'left';
60023
60024         pos = pos.charAt(0).toUpperCase() + pos.substring(1);
60025         //axisLabel(Top|Bottom|Right|Left)Style
60026         config.label = Ext.apply(config['axisLabel' + pos + 'Style'] || {}, config.label || {});
60027         config.axisTitleStyle = Ext.apply(config['axisTitle' + pos + 'Style'] || {}, config.labelTitle || {});
60028         Ext.apply(me, config);
60029         me.fields = [].concat(me.fields);
60030         this.callParent();
60031         me.labels = [];
60032         me.getId();
60033         me.labelGroup = me.chart.surface.getGroup(me.axisId + "-labels");
60034     },
60035
60036     alignment: null,
60037     grid: false,
60038     steps: 10,
60039     x: 0,
60040     y: 0,
60041     minValue: 0,
60042     maxValue: 0,
60043
60044     getId: function() {
60045         return this.axisId || (this.axisId = Ext.id(null, 'ext-axis-'));
60046     },
60047
60048     /*
60049       Called to process a view i.e to make aggregation and filtering over
60050       a store creating a substore to be used to render the axis. Since many axes
60051       may do different things on the data and we want the final result of all these
60052       operations to be rendered we need to call processView on all axes before drawing
60053       them.
60054     */
60055     processView: Ext.emptyFn,
60056
60057     drawAxis: Ext.emptyFn,
60058     addDisplayAndLabels: Ext.emptyFn
60059 });
60060
60061 /**
60062  * @class Ext.chart.axis.Axis
60063  * @extends Ext.chart.axis.Abstract
60064  * 
60065  * Defines axis for charts. The axis position, type, style can be configured.
60066  * The axes are defined in an axes array of configuration objects where the type, 
60067  * field, grid and other configuration options can be set. To know more about how 
60068  * to create a Chart please check the Chart class documentation. Here's an example for the axes part:
60069  * An example of axis for a series (in this case for an area chart that has multiple layers of yFields) could be:
60070  * 
60071   <pre><code>
60072     axes: [{
60073         type: 'Numeric',
60074         grid: true,
60075         position: 'left',
60076         fields: ['data1', 'data2', 'data3'],
60077         title: 'Number of Hits',
60078         grid: {
60079             odd: {
60080                 opacity: 1,
60081                 fill: '#ddd',
60082                 stroke: '#bbb',
60083                 'stroke-width': 1
60084             }
60085         },
60086         minimum: 0
60087     }, {
60088         type: 'Category',
60089         position: 'bottom',
60090         fields: ['name'],
60091         title: 'Month of the Year',
60092         grid: true,
60093         label: {
60094             rotate: {
60095                 degrees: 315
60096             }
60097         }
60098     }]
60099    </code></pre>
60100  * 
60101  * 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
60102  * 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. 
60103  * Both the category and numeric axes have `grid` set, which means that horizontal and vertical lines will cover the chart background. In the 
60104  * category axis the labels will be rotated so they can fit the space better.
60105  */
60106 Ext.define('Ext.chart.axis.Axis', {
60107
60108     /* Begin Definitions */
60109
60110     extend: 'Ext.chart.axis.Abstract',
60111
60112     alternateClassName: 'Ext.chart.Axis',
60113
60114     requires: ['Ext.draw.Draw'],
60115
60116     /* End Definitions */
60117
60118     /**
60119      * @cfg {Number} majorTickSteps 
60120      * If `minimum` and `maximum` are specified it forces the number of major ticks to the specified value.
60121      */
60122
60123     /**
60124      * @cfg {Number} minorTickSteps 
60125      * The number of small ticks between two major ticks. Default is zero.
60126      */
60127
60128     /**
60129      * @cfg {Number} dashSize 
60130      * The size of the dash marker. Default's 3.
60131      */
60132     dashSize: 3,
60133     
60134     /**
60135      * @cfg {String} position
60136      * Where to set the axis. Available options are `left`, `bottom`, `right`, `top`. Default's `bottom`.
60137      */
60138     position: 'bottom',
60139     
60140     // @private
60141     skipFirst: false,
60142     
60143     /**
60144      * @cfg {Number} length
60145      * Offset axis position. Default's 0.
60146      */
60147     length: 0,
60148     
60149     /**
60150      * @cfg {Number} width
60151      * Offset axis width. Default's 0.
60152      */
60153     width: 0,
60154     
60155     majorTickSteps: false,
60156
60157     // @private
60158     applyData: Ext.emptyFn,
60159
60160     // @private creates a structure with start, end and step points.
60161     calcEnds: function() {
60162         var me = this,
60163             math = Math,
60164             mmax = math.max,
60165             mmin = math.min,
60166             store = me.chart.substore || me.chart.store,
60167             series = me.chart.series.items,
60168             fields = me.fields,
60169             ln = fields.length,
60170             min = isNaN(me.minimum) ? Infinity : me.minimum,
60171             max = isNaN(me.maximum) ? -Infinity : me.maximum,
60172             prevMin = me.prevMin,
60173             prevMax = me.prevMax,
60174             aggregate = false,
60175             total = 0,
60176             excludes = [],
60177             outfrom, outto,
60178             i, l, values, rec, out;
60179
60180         //if one series is stacked I have to aggregate the values
60181         //for the scale.
60182         for (i = 0, l = series.length; !aggregate && i < l; i++) {
60183             aggregate = aggregate || series[i].stacked;
60184             excludes = series[i].__excludes || excludes;
60185         }
60186         store.each(function(record) {
60187             if (aggregate) {
60188                 if (!isFinite(min)) {
60189                     min = 0;
60190                 }
60191                 for (values = [0, 0], i = 0; i < ln; i++) {
60192                     if (excludes[i]) {
60193                         continue;
60194                     }
60195                     rec = record.get(fields[i]);
60196                     values[+(rec > 0)] += math.abs(rec);
60197                 }
60198                 max = mmax(max, -values[0], values[1]);
60199                 min = mmin(min, -values[0], values[1]);
60200             }
60201             else {
60202                 for (i = 0; i < ln; i++) {
60203                     if (excludes[i]) {
60204                         continue;
60205                     }
60206                     value = record.get(fields[i]);
60207                     max = mmax(max, value);
60208                     min = mmin(min, value);
60209                 }
60210             }
60211         });
60212         if (!isFinite(max)) {
60213             max = me.prevMax || 0;
60214         }
60215         if (!isFinite(min)) {
60216             min = me.prevMin || 0;
60217         }
60218         //normalize min max for snapEnds.
60219         if (min != max && (max != (max >> 0))) {
60220             max = (max >> 0) + 1;
60221         }
60222         out = Ext.draw.Draw.snapEnds(min, max, me.majorTickSteps !== false ?  (me.majorTickSteps +1) : me.steps);
60223         outfrom = out.from;
60224         outto = out.to;
60225         if (!isNaN(me.maximum)) {
60226             //TODO(nico) users are responsible for their own minimum/maximum values set.
60227             //Clipping should be added to remove lines in the chart which are below the axis.
60228             out.to = me.maximum;
60229         }
60230         if (!isNaN(me.minimum)) {
60231             //TODO(nico) users are responsible for their own minimum/maximum values set.
60232             //Clipping should be added to remove lines in the chart which are below the axis.
60233             out.from = me.minimum;
60234         }
60235         
60236         //Adjust after adjusting minimum and maximum
60237         out.step = (out.to - out.from) / (outto - outfrom) * out.step;
60238         
60239         if (me.adjustMaximumByMajorUnit) {
60240             out.to += out.step;
60241         }
60242         if (me.adjustMinimumByMajorUnit) {
60243             out.from -= out.step;
60244         }
60245         me.prevMin = min == max? 0 : min;
60246         me.prevMax = max;
60247         return out;
60248     },
60249
60250     /**
60251      * Renders the axis into the screen and updates it's position.
60252      */
60253     drawAxis: function (init) {
60254         var me = this,
60255             i, j,
60256             x = me.x,
60257             y = me.y,
60258             gutterX = me.chart.maxGutter[0],
60259             gutterY = me.chart.maxGutter[1],
60260             dashSize = me.dashSize,
60261             subDashesX = me.minorTickSteps || 0,
60262             subDashesY = me.minorTickSteps || 0,
60263             length = me.length,
60264             position = me.position,
60265             inflections = [],
60266             calcLabels = false,
60267             stepCalcs = me.applyData(),
60268             step = stepCalcs.step,
60269             steps = stepCalcs.steps,
60270             from = stepCalcs.from,
60271             to = stepCalcs.to,
60272             trueLength,
60273             currentX,
60274             currentY,
60275             path,
60276             prev,
60277             dashesX,
60278             dashesY,
60279             delta;
60280         
60281         //If no steps are specified
60282         //then don't draw the axis. This generally happens
60283         //when an empty store.
60284         if (me.hidden || isNaN(step) || (from == to)) {
60285             return;
60286         }
60287
60288         me.from = stepCalcs.from;
60289         me.to = stepCalcs.to;
60290         if (position == 'left' || position == 'right') {
60291             currentX = Math.floor(x) + 0.5;
60292             path = ["M", currentX, y, "l", 0, -length];
60293             trueLength = length - (gutterY * 2);
60294         }
60295         else {
60296             currentY = Math.floor(y) + 0.5;
60297             path = ["M", x, currentY, "l", length, 0];
60298             trueLength = length - (gutterX * 2);
60299         }
60300         
60301         delta = trueLength / (steps || 1);
60302         dashesX = Math.max(subDashesX +1, 0);
60303         dashesY = Math.max(subDashesY +1, 0);
60304         if (me.type == 'Numeric') {
60305             calcLabels = true;
60306             me.labels = [stepCalcs.from];
60307         }
60308         if (position == 'right' || position == 'left') {
60309             currentY = y - gutterY;
60310             currentX = x - ((position == 'left') * dashSize * 2);
60311             while (currentY >= y - gutterY - trueLength) {
60312                 path.push("M", currentX, Math.floor(currentY) + 0.5, "l", dashSize * 2 + 1, 0);
60313                 if (currentY != y - gutterY) {
60314                     for (i = 1; i < dashesY; i++) {
60315                         path.push("M", currentX + dashSize, Math.floor(currentY + delta * i / dashesY) + 0.5, "l", dashSize + 1, 0);
60316                     }
60317                 }
60318                 inflections.push([ Math.floor(x), Math.floor(currentY) ]);
60319                 currentY -= delta;
60320                 if (calcLabels) {
60321                     me.labels.push(me.labels[me.labels.length -1] + step);
60322                 }
60323                 if (delta === 0) {
60324                     break;
60325                 }
60326             }
60327             if (Math.round(currentY + delta - (y - gutterY - trueLength))) {
60328                 path.push("M", currentX, Math.floor(y - length + gutterY) + 0.5, "l", dashSize * 2 + 1, 0);
60329                 for (i = 1; i < dashesY; i++) {
60330                     path.push("M", currentX + dashSize, Math.floor(y - length + gutterY + delta * i / dashesY) + 0.5, "l", dashSize + 1, 0);
60331                 }
60332                 inflections.push([ Math.floor(x), Math.floor(currentY) ]);
60333                 if (calcLabels) {
60334                     me.labels.push(me.labels[me.labels.length -1] + step);
60335                 }
60336             }
60337         } else {
60338             currentX = x + gutterX;
60339             currentY = y - ((position == 'top') * dashSize * 2);
60340             while (currentX <= x + gutterX + trueLength) {
60341                 path.push("M", Math.floor(currentX) + 0.5, currentY, "l", 0, dashSize * 2 + 1);
60342                 if (currentX != x + gutterX) {
60343                     for (i = 1; i < dashesX; i++) {
60344                         path.push("M", Math.floor(currentX - delta * i / dashesX) + 0.5, currentY, "l", 0, dashSize + 1);
60345                     }
60346                 }
60347                 inflections.push([ Math.floor(currentX), Math.floor(y) ]);
60348                 currentX += delta;
60349                 if (calcLabels) {
60350                     me.labels.push(me.labels[me.labels.length -1] + step);
60351                 }
60352                 if (delta === 0) {
60353                     break;
60354                 }
60355             }
60356             if (Math.round(currentX - delta - (x + gutterX + trueLength))) {
60357                 path.push("M", Math.floor(x + length - gutterX) + 0.5, currentY, "l", 0, dashSize * 2 + 1);
60358                 for (i = 1; i < dashesX; i++) {
60359                     path.push("M", Math.floor(x + length - gutterX - delta * i / dashesX) + 0.5, currentY, "l", 0, dashSize + 1);
60360                 }
60361                 inflections.push([ Math.floor(currentX), Math.floor(y) ]);
60362                 if (calcLabels) {
60363                     me.labels.push(me.labels[me.labels.length -1] + step);
60364                 }
60365             }
60366         }
60367         if (!me.axis) {
60368             me.axis = me.chart.surface.add(Ext.apply({
60369                 type: 'path',
60370                 path: path
60371             }, me.axisStyle));
60372         }
60373         me.axis.setAttributes({
60374             path: path
60375         }, true);
60376         me.inflections = inflections;
60377         if (!init && me.grid) {
60378             me.drawGrid();
60379         }
60380         me.axisBBox = me.axis.getBBox();
60381         me.drawLabel();
60382     },
60383
60384     /**
60385      * Renders an horizontal and/or vertical grid into the Surface.
60386      */
60387     drawGrid: function() {
60388         var me = this,
60389             surface = me.chart.surface, 
60390             grid = me.grid,
60391             odd = grid.odd,
60392             even = grid.even,
60393             inflections = me.inflections,
60394             ln = inflections.length - ((odd || even)? 0 : 1),
60395             position = me.position,
60396             gutter = me.chart.maxGutter,
60397             width = me.width - 2,
60398             vert = false,
60399             point, prevPoint,
60400             i = 1,
60401             path = [], styles, lineWidth, dlineWidth,
60402             oddPath = [], evenPath = [];
60403         
60404         if ((gutter[1] !== 0 && (position == 'left' || position == 'right')) ||
60405             (gutter[0] !== 0 && (position == 'top' || position == 'bottom'))) {
60406             i = 0;
60407             ln++;
60408         }
60409         for (; i < ln; i++) {
60410             point = inflections[i];
60411             prevPoint = inflections[i - 1];
60412             if (odd || even) {
60413                 path = (i % 2)? oddPath : evenPath;
60414                 styles = ((i % 2)? odd : even) || {};
60415                 lineWidth = (styles.lineWidth || styles['stroke-width'] || 0) / 2;
60416                 dlineWidth = 2 * lineWidth;
60417                 if (position == 'left') {
60418                     path.push("M", prevPoint[0] + 1 + lineWidth, prevPoint[1] + 0.5 - lineWidth, 
60419                               "L", prevPoint[0] + 1 + width - lineWidth, prevPoint[1] + 0.5 - lineWidth,
60420                               "L", point[0] + 1 + width - lineWidth, point[1] + 0.5 + lineWidth,
60421                               "L", point[0] + 1 + lineWidth, point[1] + 0.5 + lineWidth, "Z");
60422                 }
60423                 else if (position == 'right') {
60424                     path.push("M", prevPoint[0] - lineWidth, prevPoint[1] + 0.5 - lineWidth, 
60425                               "L", prevPoint[0] - width + lineWidth, prevPoint[1] + 0.5 - lineWidth,
60426                               "L", point[0] - width + lineWidth, point[1] + 0.5 + lineWidth,
60427                               "L", point[0] - lineWidth, point[1] + 0.5 + lineWidth, "Z");
60428                 }
60429                 else if (position == 'top') {
60430                     path.push("M", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] + 1 + lineWidth, 
60431                               "L", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] + 1 + width - lineWidth,
60432                               "L", point[0] + 0.5 - lineWidth, point[1] + 1 + width - lineWidth,
60433                               "L", point[0] + 0.5 - lineWidth, point[1] + 1 + lineWidth, "Z");
60434                 }
60435                 else {
60436                     path.push("M", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] - lineWidth, 
60437                             "L", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] - width + lineWidth,
60438                             "L", point[0] + 0.5 - lineWidth, point[1] - width + lineWidth,
60439                             "L", point[0] + 0.5 - lineWidth, point[1] - lineWidth, "Z");
60440                 }
60441             } else {
60442                 if (position == 'left') {
60443                     path = path.concat(["M", point[0] + 0.5, point[1] + 0.5, "l", width, 0]);
60444                 }
60445                 else if (position == 'right') {
60446                     path = path.concat(["M", point[0] - 0.5, point[1] + 0.5, "l", -width, 0]);
60447                 }
60448                 else if (position == 'top') {
60449                     path = path.concat(["M", point[0] + 0.5, point[1] + 0.5, "l", 0, width]);
60450                 }
60451                 else {
60452                     path = path.concat(["M", point[0] + 0.5, point[1] - 0.5, "l", 0, -width]);
60453                 }
60454             }
60455         }
60456         if (odd || even) {
60457             if (oddPath.length) {
60458                 if (!me.gridOdd && oddPath.length) {
60459                     me.gridOdd = surface.add({
60460                         type: 'path',
60461                         path: oddPath
60462                     });
60463                 }
60464                 me.gridOdd.setAttributes(Ext.apply({
60465                     path: oddPath,
60466                     hidden: false
60467                 }, odd || {}), true);
60468             }
60469             if (evenPath.length) {
60470                 if (!me.gridEven) {
60471                     me.gridEven = surface.add({
60472                         type: 'path',
60473                         path: evenPath
60474                     });
60475                 } 
60476                 me.gridEven.setAttributes(Ext.apply({
60477                     path: evenPath,
60478                     hidden: false
60479                 }, even || {}), true);
60480             }
60481         }
60482         else {
60483             if (path.length) {
60484                 if (!me.gridLines) {
60485                     me.gridLines = me.chart.surface.add({
60486                         type: 'path',
60487                         path: path,
60488                         "stroke-width": me.lineWidth || 1,
60489                         stroke: me.gridColor || '#ccc'
60490                     });
60491                 }
60492                 me.gridLines.setAttributes({
60493                     hidden: false,
60494                     path: path
60495                 }, true);
60496             }
60497             else if (me.gridLines) {
60498                 me.gridLines.hide(true);
60499             }
60500         }
60501     },
60502
60503     //@private
60504     getOrCreateLabel: function(i, text) {
60505         var me = this,
60506             labelGroup = me.labelGroup,
60507             textLabel = labelGroup.getAt(i),
60508             surface = me.chart.surface;
60509         if (textLabel) {
60510             if (text != textLabel.attr.text) {
60511                 textLabel.setAttributes(Ext.apply({
60512                     text: text
60513                 }, me.label), true);
60514                 textLabel._bbox = textLabel.getBBox();
60515             }
60516         }
60517         else {
60518             textLabel = surface.add(Ext.apply({
60519                 group: labelGroup,
60520                 type: 'text',
60521                 x: 0,
60522                 y: 0,
60523                 text: text
60524             }, me.label));
60525             surface.renderItem(textLabel);
60526             textLabel._bbox = textLabel.getBBox();
60527         }
60528         //get untransformed bounding box
60529         if (me.label.rotation) {
60530             textLabel.setAttributes({
60531                 rotation: {
60532                     degrees: 0    
60533                 }    
60534             }, true);
60535             textLabel._ubbox = textLabel.getBBox();
60536             textLabel.setAttributes(me.label, true);
60537         } else {
60538             textLabel._ubbox = textLabel._bbox;
60539         }
60540         return textLabel;
60541     },
60542     
60543     rect2pointArray: function(sprite) {
60544         var surface = this.chart.surface,
60545             rect = surface.getBBox(sprite, true),
60546             p1 = [rect.x, rect.y],
60547             p1p = p1.slice(),
60548             p2 = [rect.x + rect.width, rect.y],
60549             p2p = p2.slice(),
60550             p3 = [rect.x + rect.width, rect.y + rect.height],
60551             p3p = p3.slice(),
60552             p4 = [rect.x, rect.y + rect.height],
60553             p4p = p4.slice(),
60554             matrix = sprite.matrix;
60555         //transform the points
60556         p1[0] = matrix.x.apply(matrix, p1p);
60557         p1[1] = matrix.y.apply(matrix, p1p);
60558         
60559         p2[0] = matrix.x.apply(matrix, p2p);
60560         p2[1] = matrix.y.apply(matrix, p2p);
60561         
60562         p3[0] = matrix.x.apply(matrix, p3p);
60563         p3[1] = matrix.y.apply(matrix, p3p);
60564         
60565         p4[0] = matrix.x.apply(matrix, p4p);
60566         p4[1] = matrix.y.apply(matrix, p4p);
60567         return [p1, p2, p3, p4];
60568     },
60569     
60570     intersect: function(l1, l2) {
60571         var r1 = this.rect2pointArray(l1),
60572             r2 = this.rect2pointArray(l2);
60573         return !!Ext.draw.Draw.intersect(r1, r2).length;
60574     },
60575     
60576     drawHorizontalLabels: function() {
60577        var  me = this,
60578             labelConf = me.label,
60579             floor = Math.floor,
60580             max = Math.max,
60581             axes = me.chart.axes,
60582             position = me.position,
60583             inflections = me.inflections,
60584             ln = inflections.length,
60585             labels = me.labels,
60586             labelGroup = me.labelGroup,
60587             maxHeight = 0,
60588             ratio,
60589             gutterY = me.chart.maxGutter[1],
60590             ubbox, bbox, point, prevX, prevLabel,
60591             projectedWidth = 0,
60592             textLabel, attr, textRight, text,
60593             label, last, x, y, i, firstLabel;
60594
60595         last = ln - 1;
60596         //get a reference to the first text label dimensions
60597         point = inflections[0];
60598         firstLabel = me.getOrCreateLabel(0, me.label.renderer(labels[0]));
60599         ratio = Math.abs(Math.sin(labelConf.rotate && (labelConf.rotate.degrees * Math.PI / 180) || 0)) >> 0;
60600         
60601         for (i = 0; i < ln; i++) {
60602             point = inflections[i];
60603             text = me.label.renderer(labels[i]);
60604             textLabel = me.getOrCreateLabel(i, text);
60605             bbox = textLabel._bbox;
60606             maxHeight = max(maxHeight, bbox.height + me.dashSize + me.label.padding);
60607             x = floor(point[0] - (ratio? bbox.height : bbox.width) / 2);
60608             if (me.chart.maxGutter[0] == 0) {
60609                 if (i == 0 && axes.findIndex('position', 'left') == -1) {
60610                     x = point[0];
60611                 }
60612                 else if (i == last && axes.findIndex('position', 'right') == -1) {
60613                     x = point[0] - bbox.width;
60614                 }
60615             }
60616             if (position == 'top') {
60617                 y = point[1] - (me.dashSize * 2) - me.label.padding - (bbox.height / 2);
60618             }
60619             else {
60620                 y = point[1] + (me.dashSize * 2) + me.label.padding + (bbox.height / 2);
60621             }
60622             
60623             textLabel.setAttributes({
60624                 hidden: false,
60625                 x: x,
60626                 y: y
60627             }, true);
60628
60629             // Skip label if there isn't available minimum space
60630             if (i != 0 && (me.intersect(textLabel, prevLabel)
60631                 || me.intersect(textLabel, firstLabel))) {
60632                 textLabel.hide(true);
60633                 continue;
60634             }
60635             
60636             prevLabel = textLabel;
60637         }
60638
60639         return maxHeight;
60640     },
60641     
60642     drawVerticalLabels: function() {
60643         var me = this,
60644             inflections = me.inflections,
60645             position = me.position,
60646             ln = inflections.length,
60647             labels = me.labels,
60648             maxWidth = 0,
60649             max = Math.max,
60650             floor = Math.floor,
60651             ceil = Math.ceil,
60652             axes = me.chart.axes,
60653             gutterY = me.chart.maxGutter[1],
60654             ubbox, bbox, point, prevLabel,
60655             projectedWidth = 0,
60656             textLabel, attr, textRight, text,
60657             label, last, x, y, i;
60658
60659         last = ln;
60660         for (i = 0; i < last; i++) {
60661             point = inflections[i];
60662             text = me.label.renderer(labels[i]);
60663             textLabel = me.getOrCreateLabel(i, text);
60664             bbox = textLabel._bbox;
60665             
60666             maxWidth = max(maxWidth, bbox.width + me.dashSize + me.label.padding);
60667             y = point[1];
60668             if (gutterY < bbox.height / 2) {
60669                 if (i == last - 1 && axes.findIndex('position', 'top') == -1) {
60670                     y = me.y - me.length + ceil(bbox.height / 2);
60671                 }
60672                 else if (i == 0 && axes.findIndex('position', 'bottom') == -1) {
60673                     y = me.y - floor(bbox.height / 2);
60674                 }
60675             }
60676             if (position == 'left') {
60677                 x = point[0] - bbox.width - me.dashSize - me.label.padding - 2;
60678             }
60679             else {
60680                 x = point[0] + me.dashSize + me.label.padding + 2;
60681             }    
60682             textLabel.setAttributes(Ext.apply({
60683                 hidden: false,
60684                 x: x,
60685                 y: y
60686             }, me.label), true);
60687             // Skip label if there isn't available minimum space
60688             if (i != 0 && me.intersect(textLabel, prevLabel)) {
60689                 textLabel.hide(true);
60690                 continue;
60691             }
60692             prevLabel = textLabel;
60693         }
60694         
60695         return maxWidth;
60696     },
60697
60698     /**
60699      * Renders the labels in the axes.
60700      */
60701     drawLabel: function() {
60702         var me = this,
60703             position = me.position,
60704             labelGroup = me.labelGroup,
60705             inflections = me.inflections,
60706             maxWidth = 0,
60707             maxHeight = 0,
60708             ln, i;
60709
60710         if (position == 'left' || position == 'right') {
60711             maxWidth = me.drawVerticalLabels();    
60712         } else {
60713             maxHeight = me.drawHorizontalLabels();
60714         }
60715
60716         // Hide unused bars
60717         ln = labelGroup.getCount();
60718         i = inflections.length;
60719         for (; i < ln; i++) {
60720             labelGroup.getAt(i).hide(true);
60721         }
60722
60723         me.bbox = {};
60724         Ext.apply(me.bbox, me.axisBBox);
60725         me.bbox.height = maxHeight;
60726         me.bbox.width = maxWidth;
60727         if (Ext.isString(me.title)) {
60728             me.drawTitle(maxWidth, maxHeight);
60729         }
60730     },
60731
60732     // @private creates the elipsis for the text.
60733     elipsis: function(sprite, text, desiredWidth, minWidth, center) {
60734         var bbox,
60735             x;
60736
60737         if (desiredWidth < minWidth) {
60738             sprite.hide(true);
60739             return false;
60740         }
60741         while (text.length > 4) {
60742             text = text.substr(0, text.length - 4) + "...";
60743             sprite.setAttributes({
60744                 text: text
60745             }, true);
60746             bbox = sprite.getBBox();
60747             if (bbox.width < desiredWidth) {
60748                 if (typeof center == 'number') {
60749                     sprite.setAttributes({
60750                         x: Math.floor(center - (bbox.width / 2))
60751                     }, true);
60752                 }
60753                 break;
60754             }
60755         }
60756         return true;
60757     },
60758
60759     /**
60760      * Updates the {@link #title} of this axis.
60761      * @param {String} title
60762      */
60763     setTitle: function(title) {
60764         this.title = title;
60765         this.drawLabel();
60766     },
60767
60768     // @private draws the title for the axis.
60769     drawTitle: function(maxWidth, maxHeight) {
60770         var me = this,
60771             position = me.position,
60772             surface = me.chart.surface,
60773             displaySprite = me.displaySprite,
60774             title = me.title,
60775             rotate = (position == 'left' || position == 'right'),
60776             x = me.x,
60777             y = me.y,
60778             base, bbox, pad;
60779
60780         if (displaySprite) {
60781             displaySprite.setAttributes({text: title}, true);
60782         } else {
60783             base = {
60784                 type: 'text',
60785                 x: 0,
60786                 y: 0,
60787                 text: title
60788             };
60789             displaySprite = me.displaySprite = surface.add(Ext.apply(base, me.axisTitleStyle, me.labelTitle));
60790             surface.renderItem(displaySprite);
60791         }
60792         bbox = displaySprite.getBBox();
60793         pad = me.dashSize + me.label.padding;
60794
60795         if (rotate) {
60796             y -= ((me.length / 2) - (bbox.height / 2));
60797             if (position == 'left') {
60798                 x -= (maxWidth + pad + (bbox.width / 2));
60799             }
60800             else {
60801                 x += (maxWidth + pad + bbox.width - (bbox.width / 2));
60802             }
60803             me.bbox.width += bbox.width + 10;
60804         }
60805         else {
60806             x += (me.length / 2) - (bbox.width * 0.5);
60807             if (position == 'top') {
60808                 y -= (maxHeight + pad + (bbox.height * 0.3));
60809             }
60810             else {
60811                 y += (maxHeight + pad + (bbox.height * 0.8));
60812             }
60813             me.bbox.height += bbox.height + 10;
60814         }
60815         displaySprite.setAttributes({
60816             translate: {
60817                 x: x,
60818                 y: y
60819             }
60820         }, true);
60821     }
60822 });
60823 /**
60824  * @class Ext.chart.axis.Category
60825  * @extends Ext.chart.axis.Axis
60826  *
60827  * A type of axis that displays items in categories. This axis is generally used to
60828  * display categorical information like names of items, month names, quarters, etc.
60829  * but no quantitative values. For that other type of information <em>Number</em>
60830  * axis are more suitable.
60831  *
60832  * As with other axis you can set the position of the axis and its title. For example:
60833  * {@img Ext.chart.axis.Category/Ext.chart.axis.Category.png Ext.chart.axis.Category chart axis}
60834     <pre><code>
60835    var store = Ext.create('Ext.data.JsonStore', {
60836         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
60837         data: [
60838             {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
60839             {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
60840             {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
60841             {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
60842             {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}                                                
60843         ]
60844     });
60845     
60846     Ext.create('Ext.chart.Chart', {
60847         renderTo: Ext.getBody(),
60848         width: 500,
60849         height: 300,
60850         store: store,
60851         axes: [{
60852             type: 'Numeric',
60853             grid: true,
60854             position: 'left',
60855             fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
60856             title: 'Sample Values',
60857             grid: {
60858                 odd: {
60859                     opacity: 1,
60860                     fill: '#ddd',
60861                     stroke: '#bbb',
60862                     'stroke-width': 1
60863                 }
60864             },
60865             minimum: 0,
60866             adjustMinimumByMajorUnit: 0
60867         }, {
60868             type: 'Category',
60869             position: 'bottom',
60870             fields: ['name'],
60871             title: 'Sample Metrics',
60872             grid: true,
60873             label: {
60874                 rotate: {
60875                     degrees: 315
60876                 }
60877             }
60878         }],
60879         series: [{
60880             type: 'area',
60881             highlight: false,
60882             axis: 'left',
60883             xField: 'name',
60884             yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
60885             style: {
60886                 opacity: 0.93
60887             }
60888         }]
60889     });
60890     </code></pre>
60891
60892     In this example with set the category axis to the bottom of the surface, bound the axis to
60893     the <em>name</em> property and set as title <em>Month of the Year</em>.
60894  */
60895
60896 Ext.define('Ext.chart.axis.Category', {
60897
60898     /* Begin Definitions */
60899
60900     extend: 'Ext.chart.axis.Axis',
60901
60902     alternateClassName: 'Ext.chart.CategoryAxis',
60903
60904     alias: 'axis.category',
60905
60906     /* End Definitions */
60907
60908     /**
60909      * A list of category names to display along this axis.
60910      *
60911      * @property categoryNames
60912      * @type Array
60913      */
60914     categoryNames: null,
60915
60916     /**
60917      * Indicates whether or not to calculate the number of categories (ticks and
60918      * labels) when there is not enough room to display all labels on the axis.
60919      * If set to true, the axis will determine the number of categories to plot.
60920      * If not, all categories will be plotted.
60921      *
60922      * @property calculateCategoryCount
60923      * @type Boolean
60924      */
60925     calculateCategoryCount: false,
60926
60927     // @private creates an array of labels to be used when rendering.
60928     setLabels: function() {
60929         var store = this.chart.store,
60930             fields = this.fields,
60931             ln = fields.length,
60932             i;
60933
60934         this.labels = [];
60935         store.each(function(record) {
60936             for (i = 0; i < ln; i++) {
60937                 this.labels.push(record.get(fields[i]));
60938             }
60939         }, this);
60940     },
60941
60942     // @private calculates labels positions and marker positions for rendering.
60943     applyData: function() {
60944         this.callParent();
60945         this.setLabels();
60946         var count = this.chart.store.getCount();
60947         return {
60948             from: 0,
60949             to: count,
60950             power: 1,
60951             step: 1,
60952             steps: count - 1
60953         };
60954     }
60955 });
60956
60957 /**
60958  * @class Ext.chart.axis.Gauge
60959  * @extends Ext.chart.axis.Abstract
60960  *
60961  * Gauge Axis is the axis to be used with a Gauge series. The Gauge axis
60962  * displays numeric data from an interval defined by the `minimum`, `maximum` and
60963  * `step` configuration properties. The placement of the numeric data can be changed
60964  * by altering the `margin` option that is set to `10` by default.
60965  *
60966  * A possible configuration for this axis would look like:
60967  *
60968             axes: [{
60969                 type: 'gauge',
60970                 position: 'gauge',
60971                 minimum: 0,
60972                 maximum: 100,
60973                 steps: 10,
60974                 margin: 7
60975             }],
60976  * 
60977  */
60978 Ext.define('Ext.chart.axis.Gauge', {
60979
60980     /* Begin Definitions */
60981
60982     extend: 'Ext.chart.axis.Abstract',
60983
60984     /* End Definitions */
60985     
60986     /**
60987      * @cfg {Number} minimum (required) the minimum value of the interval to be displayed in the axis.
60988      */
60989
60990     /**
60991      * @cfg {Number} maximum (required) the maximum value of the interval to be displayed in the axis.
60992      */
60993
60994     /**
60995      * @cfg {Number} steps (required) the number of steps and tick marks to add to the interval.
60996      */
60997
60998     /**
60999      * @cfg {Number} margin (optional) the offset positioning of the tick marks and labels in pixels. Default's 10.
61000      */
61001
61002     position: 'gauge',
61003
61004     alias: 'axis.gauge',
61005
61006     drawAxis: function(init) {
61007         var chart = this.chart,
61008             surface = chart.surface,
61009             bbox = chart.chartBBox,
61010             centerX = bbox.x + (bbox.width / 2),
61011             centerY = bbox.y + bbox.height,
61012             margin = this.margin || 10,
61013             rho = Math.min(bbox.width, 2 * bbox.height) /2 + margin,
61014             sprites = [], sprite,
61015             steps = this.steps,
61016             i, pi = Math.PI,
61017             cos = Math.cos,
61018             sin = Math.sin;
61019
61020         if (this.sprites && !chart.resizing) {
61021             this.drawLabel();
61022             return;
61023         }
61024
61025         if (this.margin >= 0) {
61026             if (!this.sprites) {
61027                 //draw circles
61028                 for (i = 0; i <= steps; i++) {
61029                     sprite = surface.add({
61030                         type: 'path',
61031                         path: ['M', centerX + (rho - margin) * cos(i / steps * pi - pi),
61032                                     centerY + (rho - margin) * sin(i / steps * pi - pi),
61033                                     'L', centerX + rho * cos(i / steps * pi - pi),
61034                                     centerY + rho * sin(i / steps * pi - pi), 'Z'],
61035                         stroke: '#ccc'
61036                     });
61037                     sprite.setAttributes({
61038                         hidden: false
61039                     }, true);
61040                     sprites.push(sprite);
61041                 }
61042             } else {
61043                 sprites = this.sprites;
61044                 //draw circles
61045                 for (i = 0; i <= steps; i++) {
61046                     sprites[i].setAttributes({
61047                         path: ['M', centerX + (rho - margin) * cos(i / steps * pi - pi),
61048                                     centerY + (rho - margin) * sin(i / steps * pi - pi),
61049                                'L', centerX + rho * cos(i / steps * pi - pi),
61050                                     centerY + rho * sin(i / steps * pi - pi), 'Z'],
61051                         stroke: '#ccc'
61052                     }, true);
61053                 }
61054             }
61055         }
61056         this.sprites = sprites;
61057         this.drawLabel();
61058         if (this.title) {
61059             this.drawTitle();
61060         }
61061     },
61062     
61063     drawTitle: function() {
61064         var me = this,
61065             chart = me.chart,
61066             surface = chart.surface,
61067             bbox = chart.chartBBox,
61068             labelSprite = me.titleSprite,
61069             labelBBox;
61070         
61071         if (!labelSprite) {
61072             me.titleSprite = labelSprite = surface.add({
61073                 type: 'text',
61074                 zIndex: 2
61075             });    
61076         }
61077         labelSprite.setAttributes(Ext.apply({
61078             text: me.title
61079         }, me.label || {}), true);
61080         labelBBox = labelSprite.getBBox();
61081         labelSprite.setAttributes({
61082             x: bbox.x + (bbox.width / 2) - (labelBBox.width / 2),
61083             y: bbox.y + bbox.height - (labelBBox.height / 2) - 4
61084         }, true);
61085     },
61086
61087     /**
61088      * Updates the {@link #title} of this axis.
61089      * @param {String} title
61090      */
61091     setTitle: function(title) {
61092         this.title = title;
61093         this.drawTitle();
61094     },
61095
61096     drawLabel: function() {
61097         var chart = this.chart,
61098             surface = chart.surface,
61099             bbox = chart.chartBBox,
61100             centerX = bbox.x + (bbox.width / 2),
61101             centerY = bbox.y + bbox.height,
61102             margin = this.margin || 10,
61103             rho = Math.min(bbox.width, 2 * bbox.height) /2 + 2 * margin,
61104             round = Math.round,
61105             labelArray = [], label,
61106             maxValue = this.maximum || 0,
61107             steps = this.steps, i = 0,
61108             adjY,
61109             pi = Math.PI,
61110             cos = Math.cos,
61111             sin = Math.sin,
61112             labelConf = this.label,
61113             renderer = labelConf.renderer || function(v) { return v; };
61114
61115         if (!this.labelArray) {
61116             //draw scale
61117             for (i = 0; i <= steps; i++) {
61118                 // TODO Adjust for height of text / 2 instead
61119                 adjY = (i === 0 || i === steps) ? 7 : 0;
61120                 label = surface.add({
61121                     type: 'text',
61122                     text: renderer(round(i / steps * maxValue)),
61123                     x: centerX + rho * cos(i / steps * pi - pi),
61124                     y: centerY + rho * sin(i / steps * pi - pi) - adjY,
61125                     'text-anchor': 'middle',
61126                     'stroke-width': 0.2,
61127                     zIndex: 10,
61128                     stroke: '#333'
61129                 });
61130                 label.setAttributes({
61131                     hidden: false
61132                 }, true);
61133                 labelArray.push(label);
61134             }
61135         }
61136         else {
61137             labelArray = this.labelArray;
61138             //draw values
61139             for (i = 0; i <= steps; i++) {
61140                 // TODO Adjust for height of text / 2 instead
61141                 adjY = (i === 0 || i === steps) ? 7 : 0;
61142                 labelArray[i].setAttributes({
61143                     text: renderer(round(i / steps * maxValue)),
61144                     x: centerX + rho * cos(i / steps * pi - pi),
61145                     y: centerY + rho * sin(i / steps * pi - pi) - adjY
61146                 }, true);
61147             }
61148         }
61149         this.labelArray = labelArray;
61150     }
61151 });
61152 /**
61153  * @class Ext.chart.axis.Numeric
61154  * @extends Ext.chart.axis.Axis
61155  *
61156  * An axis to handle numeric values. This axis is used for quantitative data as
61157  * opposed to the category axis. You can set mininum and maximum values to the
61158  * axis so that the values are bound to that. If no values are set, then the
61159  * scale will auto-adjust to the values.
61160  * {@img Ext.chart.axis.Numeric/Ext.chart.axis.Numeric.png Ext.chart.axis.Numeric chart axis}
61161  * For example:
61162
61163     <pre><code>
61164    var store = Ext.create('Ext.data.JsonStore', {
61165         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
61166         data: [
61167             {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
61168             {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
61169             {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
61170             {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
61171             {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}                                                
61172         ]
61173     });
61174     
61175     Ext.create('Ext.chart.Chart', {
61176         renderTo: Ext.getBody(),
61177         width: 500,
61178         height: 300,
61179         store: store,
61180         axes: [{
61181             type: 'Numeric',
61182             grid: true,
61183             position: 'left',
61184             fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
61185             title: 'Sample Values',
61186             grid: {
61187                 odd: {
61188                     opacity: 1,
61189                     fill: '#ddd',
61190                     stroke: '#bbb',
61191                     'stroke-width': 1
61192                 }
61193             },
61194             minimum: 0,
61195             adjustMinimumByMajorUnit: 0
61196         }, {
61197             type: 'Category',
61198             position: 'bottom',
61199             fields: ['name'],
61200             title: 'Sample Metrics',
61201             grid: true,
61202             label: {
61203                 rotate: {
61204                     degrees: 315
61205                 }
61206             }
61207         }],
61208         series: [{
61209             type: 'area',
61210             highlight: false,
61211             axis: 'left',
61212             xField: 'name',
61213             yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
61214             style: {
61215                 opacity: 0.93
61216             }
61217         }]
61218     });
61219     </code></pre>
61220
61221  *
61222  * In this example we create an axis of Numeric type. We set a minimum value so that
61223  * even if all series have values greater than zero, the grid starts at zero. We bind
61224  * the axis onto the left part of the surface by setting <em>position</em> to <em>left</em>.
61225  * We bind three different store fields to this axis by setting <em>fields</em> to an array.
61226  * We set the title of the axis to <em>Number of Hits</em> by using the <em>title</em> property.
61227  * We use a <em>grid</em> configuration to set odd background rows to a certain style and even rows
61228  * to be transparent/ignored.
61229  *
61230  *
61231  * @constructor
61232  */
61233 Ext.define('Ext.chart.axis.Numeric', {
61234
61235     /* Begin Definitions */
61236
61237     extend: 'Ext.chart.axis.Axis',
61238
61239     alternateClassName: 'Ext.chart.NumericAxis',
61240
61241     /* End Definitions */
61242
61243     type: 'numeric',
61244
61245     alias: 'axis.numeric',
61246
61247     constructor: function(config) {
61248         var me = this, label, f;
61249         me.callParent([config]);
61250         label = me.label;
61251         if (me.roundToDecimal === false) {
61252             return;
61253         }
61254         if (label.renderer) {
61255             f = label.renderer;
61256             label.renderer = function(v) {
61257                 return me.roundToDecimal( f(v), me.decimals );
61258             };
61259         } else {
61260             label.renderer = function(v) {
61261                 return me.roundToDecimal(v, me.decimals);
61262             };
61263         }
61264     },
61265     
61266     roundToDecimal: function(v, dec) {
61267         var val = Math.pow(10, dec || 0);
61268         return ((v * val) >> 0) / val;
61269     },
61270     
61271     /**
61272      * The minimum value drawn by the axis. If not set explicitly, the axis
61273      * minimum will be calculated automatically.
61274      *
61275      * @property minimum
61276      * @type Number
61277      */
61278     minimum: NaN,
61279
61280     /**
61281      * The maximum value drawn by the axis. If not set explicitly, the axis
61282      * maximum will be calculated automatically.
61283      *
61284      * @property maximum
61285      * @type Number
61286      */
61287     maximum: NaN,
61288
61289     /**
61290      * The number of decimals to round the value to.
61291      * Default's 2.
61292      *
61293      * @property decimals
61294      * @type Number
61295      */
61296     decimals: 2,
61297
61298     /**
61299      * The scaling algorithm to use on this axis. May be "linear" or
61300      * "logarithmic".
61301      *
61302      * @property scale
61303      * @type String
61304      */
61305     scale: "linear",
61306
61307     /**
61308      * Indicates the position of the axis relative to the chart
61309      *
61310      * @property position
61311      * @type String
61312      */
61313     position: 'left',
61314
61315     /**
61316      * Indicates whether to extend maximum beyond data's maximum to the nearest
61317      * majorUnit.
61318      *
61319      * @property adjustMaximumByMajorUnit
61320      * @type Boolean
61321      */
61322     adjustMaximumByMajorUnit: false,
61323
61324     /**
61325      * Indicates whether to extend the minimum beyond data's minimum to the
61326      * nearest majorUnit.
61327      *
61328      * @property adjustMinimumByMajorUnit
61329      * @type Boolean
61330      */
61331     adjustMinimumByMajorUnit: false,
61332
61333     // @private apply data.
61334     applyData: function() {
61335         this.callParent();
61336         return this.calcEnds();
61337     }
61338 });
61339
61340 /**
61341  * @class Ext.chart.axis.Radial
61342  * @extends Ext.chart.axis.Abstract
61343  * @ignore
61344  */
61345 Ext.define('Ext.chart.axis.Radial', {
61346
61347     /* Begin Definitions */
61348
61349     extend: 'Ext.chart.axis.Abstract',
61350
61351     /* End Definitions */
61352
61353     position: 'radial',
61354
61355     alias: 'axis.radial',
61356
61357     drawAxis: function(init) {
61358         var chart = this.chart,
61359             surface = chart.surface,
61360             bbox = chart.chartBBox,
61361             store = chart.store,
61362             l = store.getCount(),
61363             centerX = bbox.x + (bbox.width / 2),
61364             centerY = bbox.y + (bbox.height / 2),
61365             rho = Math.min(bbox.width, bbox.height) /2,
61366             sprites = [], sprite,
61367             steps = this.steps,
61368             i, j, pi2 = Math.PI * 2,
61369             cos = Math.cos, sin = Math.sin;
61370
61371         if (this.sprites && !chart.resizing) {
61372             this.drawLabel();
61373             return;
61374         }
61375
61376         if (!this.sprites) {
61377             //draw circles
61378             for (i = 1; i <= steps; i++) {
61379                 sprite = surface.add({
61380                     type: 'circle',
61381                     x: centerX,
61382                     y: centerY,
61383                     radius: Math.max(rho * i / steps, 0),
61384                     stroke: '#ccc'
61385                 });
61386                 sprite.setAttributes({
61387                     hidden: false
61388                 }, true);
61389                 sprites.push(sprite);
61390             }
61391             //draw lines
61392             store.each(function(rec, i) {
61393                 sprite = surface.add({
61394                     type: 'path',
61395                     path: ['M', centerX, centerY, 'L', centerX + rho * cos(i / l * pi2), centerY + rho * sin(i / l * pi2), 'Z'],
61396                     stroke: '#ccc'
61397                 });
61398                 sprite.setAttributes({
61399                     hidden: false
61400                 }, true);
61401                 sprites.push(sprite);
61402             });
61403         } else {
61404             sprites = this.sprites;
61405             //draw circles
61406             for (i = 0; i < steps; i++) {
61407                 sprites[i].setAttributes({
61408                     x: centerX,
61409                     y: centerY,
61410                     radius: Math.max(rho * (i + 1) / steps, 0),
61411                     stroke: '#ccc'
61412                 }, true);
61413             }
61414             //draw lines
61415             store.each(function(rec, j) {
61416                 sprites[i + j].setAttributes({
61417                     path: ['M', centerX, centerY, 'L', centerX + rho * cos(j / l * pi2), centerY + rho * sin(j / l * pi2), 'Z'],
61418                     stroke: '#ccc'
61419                 }, true);
61420             });
61421         }
61422         this.sprites = sprites;
61423
61424         this.drawLabel();
61425     },
61426
61427     drawLabel: function() {
61428         var chart = this.chart,
61429             surface = chart.surface,
61430             bbox = chart.chartBBox,
61431             store = chart.store,
61432             centerX = bbox.x + (bbox.width / 2),
61433             centerY = bbox.y + (bbox.height / 2),
61434             rho = Math.min(bbox.width, bbox.height) /2,
61435             max = Math.max, round = Math.round,
61436             labelArray = [], label,
61437             fields = [], nfields,
61438             categories = [], xField,
61439             aggregate = !this.maximum,
61440             maxValue = this.maximum || 0,
61441             steps = this.steps, i = 0, j, dx, dy,
61442             pi2 = Math.PI * 2,
61443             cos = Math.cos, sin = Math.sin,
61444             display = this.label.display,
61445             draw = display !== 'none',
61446             margin = 10;
61447
61448         if (!draw) {
61449             return;
61450         }
61451
61452         //get all rendered fields
61453         chart.series.each(function(series) {
61454             fields.push(series.yField);
61455             xField = series.xField;
61456         });
61457         
61458         //get maxValue to interpolate
61459         store.each(function(record, i) {
61460             if (aggregate) {
61461                 for (i = 0, nfields = fields.length; i < nfields; i++) {
61462                     maxValue = max(+record.get(fields[i]), maxValue);
61463                 }
61464             }
61465             categories.push(record.get(xField));
61466         });
61467         if (!this.labelArray) {
61468             if (display != 'categories') {
61469                 //draw scale
61470                 for (i = 1; i <= steps; i++) {
61471                     label = surface.add({
61472                         type: 'text',
61473                         text: round(i / steps * maxValue),
61474                         x: centerX,
61475                         y: centerY - rho * i / steps,
61476                         'text-anchor': 'middle',
61477                         'stroke-width': 0.1,
61478                         stroke: '#333'
61479                     });
61480                     label.setAttributes({
61481                         hidden: false
61482                     }, true);
61483                     labelArray.push(label);
61484                 }
61485             }
61486             if (display != 'scale') {
61487                 //draw text
61488                 for (j = 0, steps = categories.length; j < steps; j++) {
61489                     dx = cos(j / steps * pi2) * (rho + margin);
61490                     dy = sin(j / steps * pi2) * (rho + margin);
61491                     label = surface.add({
61492                         type: 'text',
61493                         text: categories[j],
61494                         x: centerX + dx,
61495                         y: centerY + dy,
61496                         'text-anchor': dx * dx <= 0.001? 'middle' : (dx < 0? 'end' : 'start')
61497                     });
61498                     label.setAttributes({
61499                         hidden: false
61500                     }, true);
61501                     labelArray.push(label);
61502                 }
61503             }
61504         }
61505         else {
61506             labelArray = this.labelArray;
61507             if (display != 'categories') {
61508                 //draw values
61509                 for (i = 0; i < steps; i++) {
61510                     labelArray[i].setAttributes({
61511                         text: round((i + 1) / steps * maxValue),
61512                         x: centerX,
61513                         y: centerY - rho * (i + 1) / steps,
61514                         'text-anchor': 'middle',
61515                         'stroke-width': 0.1,
61516                         stroke: '#333'
61517                     }, true);
61518                 }
61519             }
61520             if (display != 'scale') {
61521                 //draw text
61522                 for (j = 0, steps = categories.length; j < steps; j++) {
61523                     dx = cos(j / steps * pi2) * (rho + margin);
61524                     dy = sin(j / steps * pi2) * (rho + margin);
61525                     if (labelArray[i + j]) {
61526                         labelArray[i + j].setAttributes({
61527                             type: 'text',
61528                             text: categories[j],
61529                             x: centerX + dx,
61530                             y: centerY + dy,
61531                             'text-anchor': dx * dx <= 0.001? 'middle' : (dx < 0? 'end' : 'start')
61532                         }, true);
61533                     }
61534                 }
61535             }
61536         }
61537         this.labelArray = labelArray;
61538     }
61539 });
61540 /**
61541  * @author Ed Spencer
61542  * @class Ext.data.AbstractStore
61543  *
61544  * <p>AbstractStore is a superclass of {@link Ext.data.Store} and {@link Ext.data.TreeStore}. It's never used directly,
61545  * but offers a set of methods used by both of those subclasses.</p>
61546  * 
61547  * <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
61548  * you're probably looking for is {@link Ext.data.Store}. If you're still interested, here's a brief description of what 
61549  * AbstractStore is and is not.</p>
61550  * 
61551  * <p>AbstractStore provides the basic configuration for anything that can be considered a Store. It expects to be 
61552  * given a {@link Ext.data.Model Model} that represents the type of data in the Store. It also expects to be given a 
61553  * {@link Ext.data.proxy.Proxy Proxy} that handles the loading of data into the Store.</p>
61554  * 
61555  * <p>AbstractStore provides a few helpful methods such as {@link #load} and {@link #sync}, which load and save data
61556  * respectively, passing the requests through the configured {@link #proxy}. Both built-in Store subclasses add extra
61557  * behavior to each of these functions. Note also that each AbstractStore subclass has its own way of storing data - 
61558  * in {@link Ext.data.Store} the data is saved as a flat {@link Ext.util.MixedCollection MixedCollection}, whereas in
61559  * {@link Ext.data.TreeStore TreeStore} we use a {@link Ext.data.Tree} to maintain the data's hierarchy.</p>
61560  * 
61561  * TODO: Update these docs to explain about the sortable and filterable mixins.
61562  * <p>Finally, AbstractStore provides an API for sorting and filtering data via its {@link #sorters} and {@link #filters}
61563  * {@link Ext.util.MixedCollection MixedCollections}. Although this functionality is provided by AbstractStore, there's a
61564  * good description of how to use it in the introduction of {@link Ext.data.Store}.
61565  * 
61566  */
61567 Ext.define('Ext.data.AbstractStore', {
61568     requires: ['Ext.util.MixedCollection', 'Ext.data.Operation', 'Ext.util.Filter'],
61569     
61570     mixins: {
61571         observable: 'Ext.util.Observable',
61572         sortable: 'Ext.util.Sortable'
61573     },
61574     
61575     statics: {
61576         create: function(store){
61577             if (!store.isStore) {
61578                 if (!store.type) {
61579                     store.type = 'store';
61580                 }
61581                 store = Ext.createByAlias('store.' + store.type, store);
61582             }
61583             return store;
61584         }    
61585     },
61586     
61587     remoteSort  : false,
61588     remoteFilter: false,
61589
61590     /**
61591      * @cfg {String/Ext.data.proxy.Proxy/Object} proxy The Proxy to use for this Store. This can be either a string, a config
61592      * object or a Proxy instance - see {@link #setProxy} for details.
61593      */
61594
61595     /**
61596      * @cfg {Boolean/Object} autoLoad If data is not specified, and if autoLoad is true or an Object, this store's load method
61597      * is automatically called after creation. If the value of autoLoad is an Object, this Object will be passed to the store's
61598      * load method. Defaults to false.
61599      */
61600     autoLoad: false,
61601
61602     /**
61603      * @cfg {Boolean} autoSync True to automatically sync the Store with its Proxy after every edit to one of its Records.
61604      * Defaults to false.
61605      */
61606     autoSync: false,
61607
61608     /**
61609      * Sets the updating behavior based on batch synchronization. 'operation' (the default) will update the Store's
61610      * internal representation of the data after each operation of the batch has completed, 'complete' will wait until
61611      * the entire batch has been completed before updating the Store's data. 'complete' is a good choice for local
61612      * storage proxies, 'operation' is better for remote proxies, where there is a comparatively high latency.
61613      * @property batchUpdateMode
61614      * @type String
61615      */
61616     batchUpdateMode: 'operation',
61617
61618     /**
61619      * If true, any filters attached to this Store will be run after loading data, before the datachanged event is fired.
61620      * Defaults to true, ignored if {@link #remoteFilter} is true
61621      * @property filterOnLoad
61622      * @type Boolean
61623      */
61624     filterOnLoad: true,
61625
61626     /**
61627      * If true, any sorters attached to this Store will be run after loading data, before the datachanged event is fired.
61628      * Defaults to true, igored if {@link #remoteSort} is true
61629      * @property sortOnLoad
61630      * @type Boolean
61631      */
61632     sortOnLoad: true,
61633
61634     /**
61635      * True if a model was created implicitly for this Store. This happens if a fields array is passed to the Store's constructor
61636      * instead of a model constructor or name.
61637      * @property implicitModel
61638      * @type Boolean
61639      * @private
61640      */
61641     implicitModel: false,
61642
61643     /**
61644      * The string type of the Proxy to create if none is specified. This defaults to creating a {@link Ext.data.proxy.Memory memory proxy}.
61645      * @property defaultProxyType
61646      * @type String
61647      */
61648     defaultProxyType: 'memory',
61649
61650     /**
61651      * True if the Store has already been destroyed via {@link #destroyStore}. If this is true, the reference to Store should be deleted
61652      * as it will not function correctly any more.
61653      * @property isDestroyed
61654      * @type Boolean
61655      */
61656     isDestroyed: false,
61657
61658     isStore: true,
61659
61660     /**
61661      * @cfg {String} storeId Optional unique identifier for this store. If present, this Store will be registered with 
61662      * the {@link Ext.data.StoreManager}, making it easy to reuse elsewhere. Defaults to undefined.
61663      */
61664     
61665     /**
61666      * @cfg {Array} fields
61667      * This may be used in place of specifying a {@link #model} configuration. The fields should be a 
61668      * set of {@link Ext.data.Field} configuration objects. The store will automatically create a {@link Ext.data.Model}
61669      * with these fields. In general this configuration option should be avoided, it exists for the purposes of
61670      * backwards compatibility. For anything more complicated, such as specifying a particular id property or
61671      * assocations, a {@link Ext.data.Model} should be defined and specified for the {@link #model} config.
61672      */
61673
61674     sortRoot: 'data',
61675     
61676     //documented above
61677     constructor: function(config) {
61678         var me = this;
61679         
61680         me.addEvents(
61681             /**
61682              * @event add
61683              * Fired when a Model instance has been added to this Store
61684              * @param {Ext.data.Store} store The store
61685              * @param {Array} records The Model instances that were added
61686              * @param {Number} index The index at which the instances were inserted
61687              */
61688             'add',
61689
61690             /**
61691              * @event remove
61692              * Fired when a Model instance has been removed from this Store
61693              * @param {Ext.data.Store} store The Store object
61694              * @param {Ext.data.Model} record The record that was removed
61695              * @param {Number} index The index of the record that was removed
61696              */
61697             'remove',
61698             
61699             /**
61700              * @event update
61701              * Fires when a Record has been updated
61702              * @param {Store} this
61703              * @param {Ext.data.Model} record The Model instance that was updated
61704              * @param {String} operation The update operation being performed. Value may be one of:
61705              * <pre><code>
61706                Ext.data.Model.EDIT
61707                Ext.data.Model.REJECT
61708                Ext.data.Model.COMMIT
61709              * </code></pre>
61710              */
61711             'update',
61712
61713             /**
61714              * @event datachanged
61715              * Fires whenever the records in the Store have changed in some way - this could include adding or removing records,
61716              * or updating the data in existing records
61717              * @param {Ext.data.Store} this The data store
61718              */
61719             'datachanged',
61720
61721             /**
61722              * @event beforeload
61723              * Event description
61724              * @param {Ext.data.Store} store This Store
61725              * @param {Ext.data.Operation} operation The Ext.data.Operation object that will be passed to the Proxy to load the Store
61726              */
61727             'beforeload',
61728
61729             /**
61730              * @event load
61731              * Fires whenever the store reads data from a remote data source.
61732              * @param {Ext.data.Store} this
61733              * @param {Array} records An array of records
61734              * @param {Boolean} successful True if the operation was successful.
61735              */
61736             'load',
61737
61738             /**
61739              * @event beforesync
61740              * Called before a call to {@link #sync} is executed. Return false from any listener to cancel the synv
61741              * @param {Object} options Hash of all records to be synchronized, broken down into create, update and destroy
61742              */
61743             'beforesync',
61744             /**
61745              * @event clear
61746              * Fired after the {@link #removeAll} method is called.
61747              * @param {Ext.data.Store} this
61748              */
61749             'clear'
61750         );
61751         
61752         Ext.apply(me, config);
61753
61754         /**
61755          * Temporary cache in which removed model instances are kept until successfully synchronised with a Proxy,
61756          * at which point this is cleared.
61757          * @private
61758          * @property removed
61759          * @type Array
61760          */
61761         me.removed = [];
61762
61763         me.mixins.observable.constructor.apply(me, arguments);
61764         me.model = Ext.ModelManager.getModel(config.model || me.model);
61765
61766         /**
61767          * @property modelDefaults
61768          * @type Object
61769          * @private
61770          * A set of default values to be applied to every model instance added via {@link #insert} or created via {@link #create}.
61771          * This is used internally by associations to set foreign keys and other fields. See the Association classes source code
61772          * for examples. This should not need to be used by application developers.
61773          */
61774         Ext.applyIf(me, {
61775             modelDefaults: {}
61776         });
61777
61778         //Supports the 3.x style of simply passing an array of fields to the store, implicitly creating a model
61779         if (!me.model && me.fields) {
61780             me.model = Ext.define('Ext.data.Store.ImplicitModel-' + (me.storeId || Ext.id()), {
61781                 extend: 'Ext.data.Model',
61782                 fields: me.fields,
61783                 proxy: me.proxy || me.defaultProxyType
61784             });
61785
61786             delete me.fields;
61787
61788             me.implicitModel = true;
61789         }
61790
61791         //ensures that the Proxy is instantiated correctly
61792         me.setProxy(config.proxy || me.proxy || me.model.getProxy());
61793
61794         if (me.id && !me.storeId) {
61795             me.storeId = me.id;
61796             delete me.id;
61797         }
61798
61799         if (me.storeId) {
61800             Ext.data.StoreManager.register(me);
61801         }
61802         
61803         me.mixins.sortable.initSortable.call(me);        
61804         
61805         /**
61806          * The collection of {@link Ext.util.Filter Filters} currently applied to this Store
61807          * @property filters
61808          * @type Ext.util.MixedCollection
61809          */
61810         me.filters = Ext.create('Ext.util.MixedCollection');
61811         me.filters.addAll(me.decodeFilters(config.filters));
61812     },
61813
61814     /**
61815      * Sets the Store's Proxy by string, config object or Proxy instance
61816      * @param {String|Object|Ext.data.proxy.Proxy} proxy The new Proxy, which can be either a type string, a configuration object
61817      * or an Ext.data.proxy.Proxy instance
61818      * @return {Ext.data.proxy.Proxy} The attached Proxy object
61819      */
61820     setProxy: function(proxy) {
61821         var me = this;
61822         
61823         if (proxy instanceof Ext.data.proxy.Proxy) {
61824             proxy.setModel(me.model);
61825         } else {
61826             if (Ext.isString(proxy)) {
61827                 proxy = {
61828                     type: proxy    
61829                 };
61830             }
61831             Ext.applyIf(proxy, {
61832                 model: me.model
61833             });
61834             
61835             proxy = Ext.createByAlias('proxy.' + proxy.type, proxy);
61836         }
61837         
61838         me.proxy = proxy;
61839         
61840         return me.proxy;
61841     },
61842
61843     /**
61844      * Returns the proxy currently attached to this proxy instance
61845      * @return {Ext.data.proxy.Proxy} The Proxy instance
61846      */
61847     getProxy: function() {
61848         return this.proxy;
61849     },
61850
61851     //saves any phantom records
61852     create: function(data, options) {
61853         var me = this,
61854             instance = Ext.ModelManager.create(Ext.applyIf(data, me.modelDefaults), me.model.modelName),
61855             operation;
61856         
61857         options = options || {};
61858
61859         Ext.applyIf(options, {
61860             action : 'create',
61861             records: [instance]
61862         });
61863
61864         operation = Ext.create('Ext.data.Operation', options);
61865
61866         me.proxy.create(operation, me.onProxyWrite, me);
61867         
61868         return instance;
61869     },
61870
61871     read: function() {
61872         return this.load.apply(this, arguments);
61873     },
61874
61875     onProxyRead: Ext.emptyFn,
61876
61877     update: function(options) {
61878         var me = this,
61879             operation;
61880         options = options || {};
61881
61882         Ext.applyIf(options, {
61883             action : 'update',
61884             records: me.getUpdatedRecords()
61885         });
61886
61887         operation = Ext.create('Ext.data.Operation', options);
61888
61889         return me.proxy.update(operation, me.onProxyWrite, me);
61890     },
61891
61892     /**
61893      * @private
61894      * Callback for any write Operation over the Proxy. Updates the Store's MixedCollection to reflect
61895      * the updates provided by the Proxy
61896      */
61897     onProxyWrite: function(operation) {
61898         var me = this,
61899             success = operation.wasSuccessful(),
61900             records = operation.getRecords();
61901
61902         switch (operation.action) {
61903             case 'create':
61904                 me.onCreateRecords(records, operation, success);
61905                 break;
61906             case 'update':
61907                 me.onUpdateRecords(records, operation, success);
61908                 break;
61909             case 'destroy':
61910                 me.onDestroyRecords(records, operation, success);
61911                 break;
61912         }
61913
61914         if (success) {
61915             me.fireEvent('write', me, operation);
61916             me.fireEvent('datachanged', me);
61917         }
61918         //this is a callback that would have been passed to the 'create', 'update' or 'destroy' function and is optional
61919         Ext.callback(operation.callback, operation.scope || me, [records, operation, success]);
61920     },
61921
61922
61923     //tells the attached proxy to destroy the given records
61924     destroy: function(options) {
61925         var me = this,
61926             operation;
61927             
61928         options = options || {};
61929
61930         Ext.applyIf(options, {
61931             action : 'destroy',
61932             records: me.getRemovedRecords()
61933         });
61934
61935         operation = Ext.create('Ext.data.Operation', options);
61936
61937         return me.proxy.destroy(operation, me.onProxyWrite, me);
61938     },
61939
61940     /**
61941      * @private
61942      * Attached as the 'operationcomplete' event listener to a proxy's Batch object. By default just calls through
61943      * to onProxyWrite.
61944      */
61945     onBatchOperationComplete: function(batch, operation) {
61946         return this.onProxyWrite(operation);
61947     },
61948
61949     /**
61950      * @private
61951      * Attached as the 'complete' event listener to a proxy's Batch object. Iterates over the batch operations
61952      * and updates the Store's internal data MixedCollection.
61953      */
61954     onBatchComplete: function(batch, operation) {
61955         var me = this,
61956             operations = batch.operations,
61957             length = operations.length,
61958             i;
61959
61960         me.suspendEvents();
61961
61962         for (i = 0; i < length; i++) {
61963             me.onProxyWrite(operations[i]);
61964         }
61965
61966         me.resumeEvents();
61967
61968         me.fireEvent('datachanged', me);
61969     },
61970
61971     onBatchException: function(batch, operation) {
61972         // //decide what to do... could continue with the next operation
61973         // batch.start();
61974         //
61975         // //or retry the last operation
61976         // batch.retry();
61977     },
61978
61979     /**
61980      * @private
61981      * Filter function for new records.
61982      */
61983     filterNew: function(item) {
61984         // only want phantom records that are valid
61985         return item.phantom === true && item.isValid();
61986     },
61987
61988     /**
61989      * Returns all Model instances that are either currently a phantom (e.g. have no id), or have an ID but have not
61990      * yet been saved on this Store (this happens when adding a non-phantom record from another Store into this one)
61991      * @return {Array} The Model instances
61992      */
61993     getNewRecords: function() {
61994         return [];
61995     },
61996
61997     /**
61998      * Returns all Model instances that have been updated in the Store but not yet synchronized with the Proxy
61999      * @return {Array} The updated Model instances
62000      */
62001     getUpdatedRecords: function() {
62002         return [];
62003     },
62004
62005     /**
62006      * @private
62007      * Filter function for updated records.
62008      */
62009     filterUpdated: function(item) {
62010         // only want dirty records, not phantoms that are valid
62011         return item.dirty === true && item.phantom !== true && item.isValid();
62012     },
62013
62014     //returns any records that have been removed from the store but not yet destroyed on the proxy
62015     getRemovedRecords: function() {
62016         return this.removed;
62017     },
62018
62019     filter: function(filters, value) {
62020
62021     },
62022
62023     /**
62024      * @private
62025      * Normalizes an array of filter objects, ensuring that they are all Ext.util.Filter instances
62026      * @param {Array} filters The filters array
62027      * @return {Array} Array of Ext.util.Filter objects
62028      */
62029     decodeFilters: function(filters) {
62030         if (!Ext.isArray(filters)) {
62031             if (filters === undefined) {
62032                 filters = [];
62033             } else {
62034                 filters = [filters];
62035             }
62036         }
62037
62038         var length = filters.length,
62039             Filter = Ext.util.Filter,
62040             config, i;
62041
62042         for (i = 0; i < length; i++) {
62043             config = filters[i];
62044
62045             if (!(config instanceof Filter)) {
62046                 Ext.apply(config, {
62047                     root: 'data'
62048                 });
62049
62050                 //support for 3.x style filters where a function can be defined as 'fn'
62051                 if (config.fn) {
62052                     config.filterFn = config.fn;
62053                 }
62054
62055                 //support a function to be passed as a filter definition
62056                 if (typeof config == 'function') {
62057                     config = {
62058                         filterFn: config
62059                     };
62060                 }
62061
62062                 filters[i] = new Filter(config);
62063             }
62064         }
62065
62066         return filters;
62067     },
62068
62069     clearFilter: function(supressEvent) {
62070
62071     },
62072
62073     isFiltered: function() {
62074
62075     },
62076
62077     filterBy: function(fn, scope) {
62078
62079     },
62080     
62081     /**
62082      * Synchronizes the Store with its Proxy. This asks the Proxy to batch together any new, updated
62083      * and deleted records in the store, updating the Store's internal representation of the records
62084      * as each operation completes.
62085      */
62086     sync: function() {
62087         var me        = this,
62088             options   = {},
62089             toCreate  = me.getNewRecords(),
62090             toUpdate  = me.getUpdatedRecords(),
62091             toDestroy = me.getRemovedRecords(),
62092             needsSync = false;
62093
62094         if (toCreate.length > 0) {
62095             options.create = toCreate;
62096             needsSync = true;
62097         }
62098
62099         if (toUpdate.length > 0) {
62100             options.update = toUpdate;
62101             needsSync = true;
62102         }
62103
62104         if (toDestroy.length > 0) {
62105             options.destroy = toDestroy;
62106             needsSync = true;
62107         }
62108
62109         if (needsSync && me.fireEvent('beforesync', options) !== false) {
62110             me.proxy.batch(options, me.getBatchListeners());
62111         }
62112     },
62113
62114
62115     /**
62116      * @private
62117      * Returns an object which is passed in as the listeners argument to proxy.batch inside this.sync.
62118      * This is broken out into a separate function to allow for customisation of the listeners
62119      * @return {Object} The listeners object
62120      */
62121     getBatchListeners: function() {
62122         var me = this,
62123             listeners = {
62124                 scope: me,
62125                 exception: me.onBatchException
62126             };
62127
62128         if (me.batchUpdateMode == 'operation') {
62129             listeners.operationcomplete = me.onBatchOperationComplete;
62130         } else {
62131             listeners.complete = me.onBatchComplete;
62132         }
62133
62134         return listeners;
62135     },
62136
62137     //deprecated, will be removed in 5.0
62138     save: function() {
62139         return this.sync.apply(this, arguments);
62140     },
62141
62142     /**
62143      * Loads the Store using its configured {@link #proxy}.
62144      * @param {Object} options Optional config object. This is passed into the {@link Ext.data.Operation Operation}
62145      * object that is created and then sent to the proxy's {@link Ext.data.proxy.Proxy#read} function
62146      */
62147     load: function(options) {
62148         var me = this,
62149             operation;
62150
62151         options = options || {};
62152
62153         Ext.applyIf(options, {
62154             action : 'read',
62155             filters: me.filters.items,
62156             sorters: me.getSorters()
62157         });
62158         
62159         operation = Ext.create('Ext.data.Operation', options);
62160
62161         if (me.fireEvent('beforeload', me, operation) !== false) {
62162             me.loading = true;
62163             me.proxy.read(operation, me.onProxyLoad, me);
62164         }
62165         
62166         return me;
62167     },
62168
62169     /**
62170      * @private
62171      * A model instance should call this method on the Store it has been {@link Ext.data.Model#join joined} to.
62172      * @param {Ext.data.Model} record The model instance that was edited
62173      */
62174     afterEdit : function(record) {
62175         var me = this;
62176         
62177         if (me.autoSync) {
62178             me.sync();
62179         }
62180         
62181         me.fireEvent('update', me, record, Ext.data.Model.EDIT);
62182     },
62183
62184     /**
62185      * @private
62186      * A model instance should call this method on the Store it has been {@link Ext.data.Model#join joined} to..
62187      * @param {Ext.data.Model} record The model instance that was edited
62188      */
62189     afterReject : function(record) {
62190         this.fireEvent('update', this, record, Ext.data.Model.REJECT);
62191     },
62192
62193     /**
62194      * @private
62195      * A model instance should call this method on the Store it has been {@link Ext.data.Model#join joined} to.
62196      * @param {Ext.data.Model} record The model instance that was edited
62197      */
62198     afterCommit : function(record) {
62199         this.fireEvent('update', this, record, Ext.data.Model.COMMIT);
62200     },
62201
62202     clearData: Ext.emptyFn,
62203
62204     destroyStore: function() {
62205         var me = this;
62206         
62207         if (!me.isDestroyed) {
62208             if (me.storeId) {
62209                 Ext.data.StoreManager.unregister(me);
62210             }
62211             me.clearData();
62212             me.data = null;
62213             me.tree = null;
62214             // Ext.destroy(this.proxy);
62215             me.reader = me.writer = null;
62216             me.clearListeners();
62217             me.isDestroyed = true;
62218
62219             if (me.implicitModel) {
62220                 Ext.destroy(me.model);
62221             }
62222         }
62223     },
62224     
62225     doSort: function(sorterFn) {
62226         var me = this;
62227         if (me.remoteSort) {
62228             //the load function will pick up the new sorters and request the sorted data from the proxy
62229             me.load();
62230         } else {
62231             me.data.sortBy(sorterFn);
62232             me.fireEvent('datachanged', me);
62233         }
62234     },
62235
62236     getCount: Ext.emptyFn,
62237
62238     getById: Ext.emptyFn,
62239     
62240     /**
62241      * Removes all records from the store. This method does a "fast remove",
62242      * individual remove events are not called. The {@link #clear} event is
62243      * fired upon completion.
62244      */
62245     removeAll: Ext.emptyFn,
62246     // individual substores should implement a "fast" remove
62247     // and fire a clear event afterwards
62248
62249     /**
62250      * Returns true if the Store is currently performing a load operation
62251      * @return {Boolean} True if the Store is currently loading
62252      */
62253     isLoading: function() {
62254         return this.loading;
62255      }
62256 });
62257
62258 /**
62259  * @class Ext.util.Grouper
62260  * @extends Ext.util.Sorter
62261  */
62262  
62263 Ext.define('Ext.util.Grouper', {
62264
62265     /* Begin Definitions */
62266
62267     extend: 'Ext.util.Sorter',
62268
62269     /* End Definitions */
62270
62271     /**
62272      * Function description
62273      * @param {Ext.data.Model} instance The Model instance
62274      * @return {String} The group string for this model
62275      */
62276     getGroupString: function(instance) {
62277         return instance.get(this.property);
62278     }
62279 });
62280 /**
62281  * @author Ed Spencer
62282  * @class Ext.data.Store
62283  * @extends Ext.data.AbstractStore
62284  *
62285  * <p>The Store class encapsulates a client side cache of {@link Ext.data.Model Model} objects. Stores load
62286  * data via a {@link Ext.data.proxy.Proxy Proxy}, and also provide functions for {@link #sort sorting},
62287  * {@link #filter filtering} and querying the {@link Ext.data.Model model} instances contained within it.</p>
62288  *
62289  * <p>Creating a Store is easy - we just tell it the Model and the Proxy to use to load and save its data:</p>
62290  *
62291 <pre><code>
62292 // Set up a {@link Ext.data.Model model} to use in our Store
62293 Ext.define('User', {
62294     extend: 'Ext.data.Model',
62295     fields: [
62296         {name: 'firstName', type: 'string'},
62297         {name: 'lastName',  type: 'string'},
62298         {name: 'age',       type: 'int'},
62299         {name: 'eyeColor',  type: 'string'}
62300     ]
62301 });
62302
62303 var myStore = new Ext.data.Store({
62304     model: 'User',
62305     proxy: {
62306         type: 'ajax',
62307         url : '/users.json',
62308         reader: {
62309             type: 'json',
62310             root: 'users'
62311         }
62312     },
62313     autoLoad: true
62314 });
62315 </code></pre>
62316
62317  * <p>In the example above we configured an AJAX proxy to load data from the url '/users.json'. We told our Proxy
62318  * to use a {@link Ext.data.reader.Json JsonReader} to parse the response from the server into Model object -
62319  * {@link Ext.data.reader.Json see the docs on JsonReader} for details.</p>
62320  *
62321  * <p><u>Inline data</u></p>
62322  *
62323  * <p>Stores can also load data inline. Internally, Store converts each of the objects we pass in as {@link #data}
62324  * into Model instances:</p>
62325  *
62326 <pre><code>
62327 new Ext.data.Store({
62328     model: 'User',
62329     data : [
62330         {firstName: 'Ed',    lastName: 'Spencer'},
62331         {firstName: 'Tommy', lastName: 'Maintz'},
62332         {firstName: 'Aaron', lastName: 'Conran'},
62333         {firstName: 'Jamie', lastName: 'Avins'}
62334     ]
62335 });
62336 </code></pre>
62337  *
62338  * <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
62339  * to be processed by a {@link Ext.data.reader.Reader reader}). If your inline data requires processing to decode the data structure,
62340  * use a {@link Ext.data.proxy.Memory MemoryProxy} instead (see the {@link Ext.data.proxy.Memory MemoryProxy} docs for an example).</p>
62341  *
62342  * <p>Additional data can also be loaded locally using {@link #add}.</p>
62343  *
62344  * <p><u>Loading Nested Data</u></p>
62345  *
62346  * <p>Applications often need to load sets of associated data - for example a CRM system might load a User and her Orders.
62347  * 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
62348  * and allow the Reader to automatically populate the associated models. Below is a brief example, see the {@link Ext.data.reader.Reader} intro
62349  * docs for a full explanation:</p>
62350  *
62351 <pre><code>
62352 var store = new Ext.data.Store({
62353     autoLoad: true,
62354     model: "User",
62355     proxy: {
62356         type: 'ajax',
62357         url : 'users.json',
62358         reader: {
62359             type: 'json',
62360             root: 'users'
62361         }
62362     }
62363 });
62364 </code></pre>
62365  *
62366  * <p>Which would consume a response like this:</p>
62367  *
62368 <pre><code>
62369 {
62370     "users": [
62371         {
62372             "id": 1,
62373             "name": "Ed",
62374             "orders": [
62375                 {
62376                     "id": 10,
62377                     "total": 10.76,
62378                     "status": "invoiced"
62379                 },
62380                 {
62381                     "id": 11,
62382                     "total": 13.45,
62383                     "status": "shipped"
62384                 }
62385             ]
62386         }
62387     ]
62388 }
62389 </code></pre>
62390  *
62391  * <p>See the {@link Ext.data.reader.Reader} intro docs for a full explanation.</p>
62392  *
62393  * <p><u>Filtering and Sorting</u></p>
62394  *
62395  * <p>Stores can be sorted and filtered - in both cases either remotely or locally. The {@link #sorters} and {@link #filters} are
62396  * held inside {@link Ext.util.MixedCollection MixedCollection} instances to make them easy to manage. Usually it is sufficient to
62397  * either just specify sorters and filters in the Store configuration or call {@link #sort} or {@link #filter}:
62398  *
62399 <pre><code>
62400 var store = new Ext.data.Store({
62401     model: 'User',
62402     sorters: [
62403         {
62404             property : 'age',
62405             direction: 'DESC'
62406         },
62407         {
62408             property : 'firstName',
62409             direction: 'ASC'
62410         }
62411     ],
62412
62413     filters: [
62414         {
62415             property: 'firstName',
62416             value   : /Ed/
62417         }
62418     ]
62419 });
62420 </code></pre>
62421  *
62422  * <p>The new Store will keep the configured sorters and filters in the MixedCollection instances mentioned above. By default, sorting
62423  * and filtering are both performed locally by the Store - see {@link #remoteSort} and {@link #remoteFilter} to allow the server to
62424  * perform these operations instead.</p>
62425  *
62426  * <p>Filtering and sorting after the Store has been instantiated is also easy. Calling {@link #filter} adds another filter to the Store
62427  * and automatically filters the dataset (calling {@link #filter} with no arguments simply re-applies all existing filters). Note that by
62428  * default {@link #sortOnFilter} is set to true, which means that your sorters are automatically reapplied if using local sorting.</p>
62429  *
62430 <pre><code>
62431 store.filter('eyeColor', 'Brown');
62432 </code></pre>
62433  *
62434  * <p>Change the sorting at any time by calling {@link #sort}:</p>
62435  *
62436 <pre><code>
62437 store.sort('height', 'ASC');
62438 </code></pre>
62439  *
62440  * <p>Note that all existing sorters will be removed in favor of the new sorter data (if {@link #sort} is called with no arguments,
62441  * the existing sorters are just reapplied instead of being removed). To keep existing sorters and add new ones, just add them
62442  * to the MixedCollection:</p>
62443  *
62444 <pre><code>
62445 store.sorters.add(new Ext.util.Sorter({
62446     property : 'shoeSize',
62447     direction: 'ASC'
62448 }));
62449
62450 store.sort();
62451 </code></pre>
62452  *
62453  * <p><u>Registering with StoreManager</u></p>
62454  *
62455  * <p>Any Store that is instantiated with a {@link #storeId} will automatically be registed with the {@link Ext.data.StoreManager StoreManager}.
62456  * This makes it easy to reuse the same store in multiple views:</p>
62457  *
62458  <pre><code>
62459 //this store can be used several times
62460 new Ext.data.Store({
62461     model: 'User',
62462     storeId: 'usersStore'
62463 });
62464
62465 new Ext.List({
62466     store: 'usersStore',
62467
62468     //other config goes here
62469 });
62470
62471 new Ext.view.View({
62472     store: 'usersStore',
62473
62474     //other config goes here
62475 });
62476 </code></pre>
62477  *
62478  * <p><u>Further Reading</u></p>
62479  *
62480  * <p>Stores are backed up by an ecosystem of classes that enables their operation. To gain a full understanding of these
62481  * pieces and how they fit together, see:</p>
62482  *
62483  * <ul style="list-style-type: disc; padding-left: 25px">
62484  * <li>{@link Ext.data.proxy.Proxy Proxy} - overview of what Proxies are and how they are used</li>
62485  * <li>{@link Ext.data.Model Model} - the core class in the data package</li>
62486  * <li>{@link Ext.data.reader.Reader Reader} - used by any subclass of {@link Ext.data.proxy.Server ServerProxy} to read a response</li>
62487  * </ul>
62488  *
62489  * @constructor
62490  * @param {Object} config Optional config object
62491  */
62492 Ext.define('Ext.data.Store', {
62493     extend: 'Ext.data.AbstractStore',
62494
62495     alias: 'store.store',
62496
62497     requires: ['Ext.ModelManager', 'Ext.data.Model', 'Ext.util.Grouper'],
62498     uses: ['Ext.data.proxy.Memory'],
62499
62500     /**
62501      * @cfg {Boolean} remoteSort
62502      * True to defer any sorting operation to the server. If false, sorting is done locally on the client. Defaults to <tt>false</tt>.
62503      */
62504     remoteSort: false,
62505
62506     /**
62507      * @cfg {Boolean} remoteFilter
62508      * True to defer any filtering operation to the server. If false, filtering is done locally on the client. Defaults to <tt>false</tt>.
62509      */
62510     remoteFilter: false,
62511     
62512     /**
62513      * @cfg {Boolean} remoteGroup
62514      * True if the grouping should apply on the server side, false if it is local only (defaults to false).  If the
62515      * grouping is local, it can be applied immediately to the data.  If it is remote, then it will simply act as a
62516      * helper, automatically sending the grouping information to the server.
62517      */
62518     remoteGroup : false,
62519
62520     /**
62521      * @cfg {String/Ext.data.proxy.Proxy/Object} proxy The Proxy to use for this Store. This can be either a string, a config
62522      * object or a Proxy instance - see {@link #setProxy} for details.
62523      */
62524
62525     /**
62526      * @cfg {Array} data Optional array of Model instances or data objects to load locally. See "Inline data" above for details.
62527      */
62528
62529     /**
62530      * @cfg {String} model The {@link Ext.data.Model} associated with this store
62531      */
62532
62533     /**
62534      * The (optional) field by which to group data in the store. Internally, grouping is very similar to sorting - the
62535      * groupField and {@link #groupDir} are injected as the first sorter (see {@link #sort}). Stores support a single
62536      * level of grouping, and groups can be fetched via the {@link #getGroups} method.
62537      * @property groupField
62538      * @type String
62539      */
62540     groupField: undefined,
62541
62542     /**
62543      * The direction in which sorting should be applied when grouping. Defaults to "ASC" - the other supported value is "DESC"
62544      * @property groupDir
62545      * @type String
62546      */
62547     groupDir: "ASC",
62548
62549     /**
62550      * The number of records considered to form a 'page'. This is used to power the built-in
62551      * paging using the nextPage and previousPage functions. Defaults to 25.
62552      * @property pageSize
62553      * @type Number
62554      */
62555     pageSize: 25,
62556
62557     /**
62558      * The page that the Store has most recently loaded (see {@link #loadPage})
62559      * @property currentPage
62560      * @type Number
62561      */
62562     currentPage: 1,
62563
62564     /**
62565      * @cfg {Boolean} clearOnPageLoad True to empty the store when loading another page via {@link #loadPage},
62566      * {@link #nextPage} or {@link #previousPage} (defaults to true). Setting to false keeps existing records, allowing
62567      * large data sets to be loaded one page at a time but rendered all together.
62568      */
62569     clearOnPageLoad: true,
62570
62571     /**
62572      * True if the Store is currently loading via its Proxy
62573      * @property loading
62574      * @type Boolean
62575      * @private
62576      */
62577     loading: false,
62578
62579     /**
62580      * @cfg {Boolean} sortOnFilter For local filtering only, causes {@link #sort} to be called whenever {@link #filter} is called,
62581      * causing the sorters to be reapplied after filtering. Defaults to true
62582      */
62583     sortOnFilter: true,
62584     
62585     /**
62586      * @cfg {Boolean} buffered
62587      * Allow the store to buffer and pre-fetch pages of records. This is to be used in conjunction with a view will
62588      * tell the store to pre-fetch records ahead of a time.
62589      */
62590     buffered: false,
62591     
62592     /**
62593      * @cfg {Number} purgePageCount 
62594      * The number of pages to keep in the cache before purging additional records. A value of 0 indicates to never purge the prefetched data.
62595      * This option is only relevant when the {@link #buffered} option is set to true.
62596      */
62597     purgePageCount: 5,
62598
62599     isStore: true,
62600
62601     //documented above
62602     constructor: function(config) {
62603         config = config || {};
62604
62605         var me = this,
62606             groupers = config.groupers,
62607             proxy,
62608             data;
62609             
62610         if (config.buffered || me.buffered) {
62611             me.prefetchData = Ext.create('Ext.util.MixedCollection', false, function(record) {
62612                 return record.index;
62613             });
62614             me.pendingRequests = [];
62615             me.pagesRequested = [];
62616             
62617             me.sortOnLoad = false;
62618             me.filterOnLoad = false;
62619         }
62620             
62621         me.addEvents(
62622             /**
62623              * @event beforeprefetch
62624              * Fires before a prefetch occurs. Return false to cancel.
62625              * @param {Ext.data.store} this
62626              * @param {Ext.data.Operation} operation The associated operation
62627              */
62628             'beforeprefetch',
62629             /**
62630              * @event groupchange
62631              * Fired whenever the grouping in the grid changes
62632              * @param {Ext.data.Store} store The store
62633              * @param {Array} groupers The array of grouper objects
62634              */
62635             'groupchange',
62636             /**
62637              * @event load
62638              * Fires whenever records have been prefetched
62639              * @param {Ext.data.store} this
62640              * @param {Array} records An array of records
62641              * @param {Boolean} successful True if the operation was successful.
62642              * @param {Ext.data.Operation} operation The associated operation
62643              */
62644             'prefetch'
62645         );
62646         data = config.data || me.data;
62647
62648         /**
62649          * The MixedCollection that holds this store's local cache of records
62650          * @property data
62651          * @type Ext.util.MixedCollection
62652          */
62653         me.data = Ext.create('Ext.util.MixedCollection', false, function(record) {
62654             return record.internalId;
62655         });
62656
62657         if (data) {
62658             me.inlineData = data;
62659             delete config.data;
62660         }
62661         
62662         if (!groupers && config.groupField) {
62663             groupers = [{
62664                 property : config.groupField,
62665                 direction: config.groupDir
62666             }];
62667         }
62668         delete config.groupers;
62669         
62670         /**
62671          * The collection of {@link Ext.util.Grouper Groupers} currently applied to this Store
62672          * @property groupers
62673          * @type Ext.util.MixedCollection
62674          */
62675         me.groupers = Ext.create('Ext.util.MixedCollection');
62676         me.groupers.addAll(me.decodeGroupers(groupers));
62677
62678         this.callParent([config]);
62679         
62680         if (me.groupers.items.length) {
62681             me.sort(me.groupers.items, 'prepend', false);
62682         }
62683
62684         proxy = me.proxy;
62685         data = me.inlineData;
62686
62687         if (data) {
62688             if (proxy instanceof Ext.data.proxy.Memory) {
62689                 proxy.data = data;
62690                 me.read();
62691             } else {
62692                 me.add.apply(me, data);
62693             }
62694
62695             me.sort();
62696             delete me.inlineData;
62697         } else if (me.autoLoad) {
62698             Ext.defer(me.load, 10, me, [typeof me.autoLoad === 'object' ? me.autoLoad: undefined]);
62699             // Remove the defer call, we may need reinstate this at some point, but currently it's not obvious why it's here.
62700             // this.load(typeof this.autoLoad == 'object' ? this.autoLoad : undefined);
62701         }
62702     },
62703     
62704     onBeforeSort: function() {
62705         this.sort(this.groupers.items, 'prepend', false);
62706     },
62707     
62708     /**
62709      * @private
62710      * Normalizes an array of grouper objects, ensuring that they are all Ext.util.Grouper instances
62711      * @param {Array} groupers The groupers array
62712      * @return {Array} Array of Ext.util.Grouper objects
62713      */
62714     decodeGroupers: function(groupers) {
62715         if (!Ext.isArray(groupers)) {
62716             if (groupers === undefined) {
62717                 groupers = [];
62718             } else {
62719                 groupers = [groupers];
62720             }
62721         }
62722
62723         var length  = groupers.length,
62724             Grouper = Ext.util.Grouper,
62725             config, i;
62726
62727         for (i = 0; i < length; i++) {
62728             config = groupers[i];
62729
62730             if (!(config instanceof Grouper)) {
62731                 if (Ext.isString(config)) {
62732                     config = {
62733                         property: config
62734                     };
62735                 }
62736                 
62737                 Ext.applyIf(config, {
62738                     root     : 'data',
62739                     direction: "ASC"
62740                 });
62741
62742                 //support for 3.x style sorters where a function can be defined as 'fn'
62743                 if (config.fn) {
62744                     config.sorterFn = config.fn;
62745                 }
62746
62747                 //support a function to be passed as a sorter definition
62748                 if (typeof config == 'function') {
62749                     config = {
62750                         sorterFn: config
62751                     };
62752                 }
62753
62754                 groupers[i] = new Grouper(config);
62755             }
62756         }
62757
62758         return groupers;
62759     },
62760     
62761     /**
62762      * Group data in the store
62763      * @param {String|Array} groupers Either a string name of one of the fields in this Store's configured {@link Ext.data.Model Model},
62764      * or an Array of grouper configurations.
62765      * @param {String} direction The overall direction to group the data by. Defaults to "ASC".
62766      */
62767     group: function(groupers, direction) {
62768         var me = this,
62769             grouper,
62770             newGroupers;
62771             
62772         if (Ext.isArray(groupers)) {
62773             newGroupers = groupers;
62774         } else if (Ext.isObject(groupers)) {
62775             newGroupers = [groupers];
62776         } else if (Ext.isString(groupers)) {
62777             grouper = me.groupers.get(groupers);
62778
62779             if (!grouper) {
62780                 grouper = {
62781                     property : groupers,
62782                     direction: direction
62783                 };
62784                 newGroupers = [grouper];
62785             } else if (direction === undefined) {
62786                 grouper.toggle();
62787             } else {
62788                 grouper.setDirection(direction);
62789             }
62790         }
62791         
62792         if (newGroupers && newGroupers.length) {
62793             newGroupers = me.decodeGroupers(newGroupers);
62794             me.groupers.clear();
62795             me.groupers.addAll(newGroupers);
62796         }
62797         
62798         if (me.remoteGroup) {
62799             me.load({
62800                 scope: me,
62801                 callback: me.fireGroupChange
62802             });
62803         } else {
62804             me.sort();
62805             me.fireEvent('groupchange', me, me.groupers);
62806         }
62807     },
62808     
62809     /**
62810      * Clear any groupers in the store
62811      */
62812     clearGrouping: function(){
62813         var me = this;
62814         // Clear any groupers we pushed on to the sorters
62815         me.groupers.each(function(grouper){
62816             me.sorters.remove(grouper);
62817         });
62818         me.groupers.clear();
62819         if (me.remoteGroup) {
62820             me.load({
62821                 scope: me,
62822                 callback: me.fireGroupChange
62823             });
62824         } else {
62825             me.sort();
62826             me.fireEvent('groupchange', me, me.groupers);
62827         }
62828     },
62829     
62830     /**
62831      * Checks if the store is currently grouped
62832      * @return {Boolean} True if the store is grouped.
62833      */
62834     isGrouped: function() {
62835         return this.groupers.getCount() > 0;    
62836     },
62837     
62838     /**
62839      * Fires the groupchange event. Abstracted out so we can use it
62840      * as a callback
62841      * @private
62842      */
62843     fireGroupChange: function(){
62844         this.fireEvent('groupchange', this, this.groupers);    
62845     },
62846
62847     /**
62848      * Returns an object containing the result of applying grouping to the records in this store. See {@link #groupField},
62849      * {@link #groupDir} and {@link #getGroupString}. Example for a store containing records with a color field:
62850 <pre><code>
62851 var myStore = new Ext.data.Store({
62852     groupField: 'color',
62853     groupDir  : 'DESC'
62854 });
62855
62856 myStore.getGroups(); //returns:
62857 [
62858     {
62859         name: 'yellow',
62860         children: [
62861             //all records where the color field is 'yellow'
62862         ]
62863     },
62864     {
62865         name: 'red',
62866         children: [
62867             //all records where the color field is 'red'
62868         ]
62869     }
62870 ]
62871 </code></pre>
62872      * @param {String} groupName (Optional) Pass in an optional groupName argument to access a specific group as defined by {@link #getGroupString}
62873      * @return {Array} The grouped data
62874      */
62875     getGroups: function(requestGroupString) {
62876         var records = this.data.items,
62877             length = records.length,
62878             groups = [],
62879             pointers = {},
62880             record,
62881             groupStr,
62882             group,
62883             i;
62884
62885         for (i = 0; i < length; i++) {
62886             record = records[i];
62887             groupStr = this.getGroupString(record);
62888             group = pointers[groupStr];
62889
62890             if (group === undefined) {
62891                 group = {
62892                     name: groupStr,
62893                     children: []
62894                 };
62895
62896                 groups.push(group);
62897                 pointers[groupStr] = group;
62898             }
62899
62900             group.children.push(record);
62901         }
62902
62903         return requestGroupString ? pointers[requestGroupString] : groups;
62904     },
62905
62906     /**
62907      * @private
62908      * For a given set of records and a Grouper, returns an array of arrays - each of which is the set of records
62909      * matching a certain group.
62910      */
62911     getGroupsForGrouper: function(records, grouper) {
62912         var length = records.length,
62913             groups = [],
62914             oldValue,
62915             newValue,
62916             record,
62917             group,
62918             i;
62919
62920         for (i = 0; i < length; i++) {
62921             record = records[i];
62922             newValue = grouper.getGroupString(record);
62923
62924             if (newValue !== oldValue) {
62925                 group = {
62926                     name: newValue,
62927                     grouper: grouper,
62928                     records: []
62929                 };
62930                 groups.push(group);
62931             }
62932
62933             group.records.push(record);
62934
62935             oldValue = newValue;
62936         }
62937
62938         return groups;
62939     },
62940
62941     /**
62942      * @private
62943      * This is used recursively to gather the records into the configured Groupers. The data MUST have been sorted for
62944      * this to work properly (see {@link #getGroupData} and {@link #getGroupsForGrouper}) Most of the work is done by
62945      * {@link #getGroupsForGrouper} - this function largely just handles the recursion.
62946      * @param {Array} records The set or subset of records to group
62947      * @param {Number} grouperIndex The grouper index to retrieve
62948      * @return {Array} The grouped records
62949      */
62950     getGroupsForGrouperIndex: function(records, grouperIndex) {
62951         var me = this,
62952             groupers = me.groupers,
62953             grouper = groupers.getAt(grouperIndex),
62954             groups = me.getGroupsForGrouper(records, grouper),
62955             length = groups.length,
62956             i;
62957
62958         if (grouperIndex + 1 < groupers.length) {
62959             for (i = 0; i < length; i++) {
62960                 groups[i].children = me.getGroupsForGrouperIndex(groups[i].records, grouperIndex + 1);
62961             }
62962         }
62963
62964         for (i = 0; i < length; i++) {
62965             groups[i].depth = grouperIndex;
62966         }
62967
62968         return groups;
62969     },
62970
62971     /**
62972      * @private
62973      * <p>Returns records grouped by the configured {@link #groupers grouper} configuration. Sample return value (in
62974      * this case grouping by genre and then author in a fictional books dataset):</p>
62975 <pre><code>
62976 [
62977     {
62978         name: 'Fantasy',
62979         depth: 0,
62980         records: [
62981             //book1, book2, book3, book4
62982         ],
62983         children: [
62984             {
62985                 name: 'Rowling',
62986                 depth: 1,
62987                 records: [
62988                     //book1, book2
62989                 ]
62990             },
62991             {
62992                 name: 'Tolkein',
62993                 depth: 1,
62994                 records: [
62995                     //book3, book4
62996                 ]
62997             }
62998         ]
62999     }
63000 ]
63001 </code></pre>
63002      * @param {Boolean} sort True to call {@link #sort} before finding groups. Sorting is required to make grouping
63003      * function correctly so this should only be set to false if the Store is known to already be sorted correctly
63004      * (defaults to true)
63005      * @return {Array} The group data
63006      */
63007     getGroupData: function(sort) {
63008         var me = this;
63009         if (sort !== false) {
63010             me.sort();
63011         }
63012
63013         return me.getGroupsForGrouperIndex(me.data.items, 0);
63014     },
63015
63016     /**
63017      * <p>Returns the string to group on for a given model instance. The default implementation of this method returns
63018      * the model's {@link #groupField}, but this can be overridden to group by an arbitrary string. For example, to
63019      * group by the first letter of a model's 'name' field, use the following code:</p>
63020 <pre><code>
63021 new Ext.data.Store({
63022     groupDir: 'ASC',
63023     getGroupString: function(instance) {
63024         return instance.get('name')[0];
63025     }
63026 });
63027 </code></pre>
63028      * @param {Ext.data.Model} instance The model instance
63029      * @return {String} The string to compare when forming groups
63030      */
63031     getGroupString: function(instance) {
63032         var group = this.groupers.first();
63033         if (group) {
63034             return instance.get(group.property);
63035         }
63036         return '';
63037     },
63038     /**
63039      * Inserts Model instances into the Store at the given index and fires the {@link #add} event.
63040      * See also <code>{@link #add}</code>.
63041      * @param {Number} index The start index at which to insert the passed Records.
63042      * @param {Ext.data.Model[]} records An Array of Ext.data.Model objects to add to the cache.
63043      */
63044     insert: function(index, records) {
63045         var me = this,
63046             sync = false,
63047             i,
63048             record,
63049             len;
63050
63051         records = [].concat(records);
63052         for (i = 0, len = records.length; i < len; i++) {
63053             record = me.createModel(records[i]);
63054             record.set(me.modelDefaults);
63055             // reassign the model in the array in case it wasn't created yet
63056             records[i] = record;
63057             
63058             me.data.insert(index + i, record);
63059             record.join(me);
63060
63061             sync = sync || record.phantom === true;
63062         }
63063
63064         if (me.snapshot) {
63065             me.snapshot.addAll(records);
63066         }
63067
63068         me.fireEvent('add', me, records, index);
63069         me.fireEvent('datachanged', me);
63070         if (me.autoSync && sync) {
63071             me.sync();
63072         }
63073     },
63074
63075     /**
63076      * Adds Model instances to the Store by instantiating them based on a JavaScript object. When adding already-
63077      * instantiated Models, use {@link #insert} instead. The instances will be added at the end of the existing collection.
63078      * This method accepts either a single argument array of Model instances or any number of model instance arguments.
63079      * Sample usage:
63080      *
63081 <pre><code>
63082 myStore.add({some: 'data'}, {some: 'other data'});
63083 </code></pre>
63084      *
63085      * @param {Object} data The data for each model
63086      * @return {Array} The array of newly created model instances
63087      */
63088     add: function(records) {
63089         //accept both a single-argument array of records, or any number of record arguments
63090         if (!Ext.isArray(records)) {
63091             records = Array.prototype.slice.apply(arguments);
63092         }
63093
63094         var me = this,
63095             i = 0,
63096             length = records.length,
63097             record;
63098
63099         for (; i < length; i++) {
63100             record = me.createModel(records[i]);
63101             // reassign the model in the array in case it wasn't created yet
63102             records[i] = record;
63103         }
63104
63105         me.insert(me.data.length, records);
63106
63107         return records;
63108     },
63109
63110     /**
63111      * Converts a literal to a model, if it's not a model already
63112      * @private
63113      * @param record {Ext.data.Model/Object} The record to create
63114      * @return {Ext.data.Model}
63115      */
63116     createModel: function(record) {
63117         if (!record.isModel) {
63118             record = Ext.ModelManager.create(record, this.model);
63119         }
63120
63121         return record;
63122     },
63123
63124     /**
63125      * Calls the specified function for each of the {@link Ext.data.Model Records} in the cache.
63126      * @param {Function} fn The function to call. The {@link Ext.data.Model Record} is passed as the first parameter.
63127      * Returning <tt>false</tt> aborts and exits the iteration.
63128      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed.
63129      * Defaults to the current {@link Ext.data.Model Record} in the iteration.
63130      */
63131     each: function(fn, scope) {
63132         this.data.each(fn, scope);
63133     },
63134
63135     /**
63136      * Removes the given record from the Store, firing the 'remove' event for each instance that is removed, plus a single
63137      * 'datachanged' event after removal.
63138      * @param {Ext.data.Model/Array} records The Ext.data.Model instance or array of instances to remove
63139      */
63140     remove: function(records, /* private */ isMove) {
63141         if (!Ext.isArray(records)) {
63142             records = [records];
63143         }
63144
63145         /*
63146          * Pass the isMove parameter if we know we're going to be re-inserting this record
63147          */
63148         isMove = isMove === true;
63149         var me = this,
63150             sync = false,
63151             i = 0,
63152             length = records.length,
63153             isPhantom,
63154             index,
63155             record;
63156
63157         for (; i < length; i++) {
63158             record = records[i];
63159             index = me.data.indexOf(record);
63160             
63161             if (me.snapshot) {
63162                 me.snapshot.remove(record);
63163             }
63164             
63165             if (index > -1) {
63166                 isPhantom = record.phantom === true;
63167                 if (!isMove && !isPhantom) {
63168                     // don't push phantom records onto removed
63169                     me.removed.push(record);
63170                 }
63171
63172                 record.unjoin(me);
63173                 me.data.remove(record);
63174                 sync = sync || !isPhantom;
63175
63176                 me.fireEvent('remove', me, record, index);
63177             }
63178         }
63179
63180         me.fireEvent('datachanged', me);
63181         if (!isMove && me.autoSync && sync) {
63182             me.sync();
63183         }
63184     },
63185
63186     /**
63187      * Removes the model instance at the given index
63188      * @param {Number} index The record index
63189      */
63190     removeAt: function(index) {
63191         var record = this.getAt(index);
63192
63193         if (record) {
63194             this.remove(record);
63195         }
63196     },
63197
63198     /**
63199      * <p>Loads data into the Store via the configured {@link #proxy}. This uses the Proxy to make an
63200      * asynchronous call to whatever storage backend the Proxy uses, automatically adding the retrieved
63201      * instances into the Store and calling an optional callback if required. Example usage:</p>
63202      *
63203 <pre><code>
63204 store.load({
63205     scope   : this,
63206     callback: function(records, operation, success) {
63207         //the {@link Ext.data.Operation operation} object contains all of the details of the load operation
63208         console.log(records);
63209     }
63210 });
63211 </code></pre>
63212      *
63213      * <p>If the callback scope does not need to be set, a function can simply be passed:</p>
63214      *
63215 <pre><code>
63216 store.load(function(records, operation, success) {
63217     console.log('loaded records');
63218 });
63219 </code></pre>
63220      *
63221      * @param {Object/Function} options Optional config object, passed into the Ext.data.Operation object before loading.
63222      */
63223     load: function(options) {
63224         var me = this;
63225             
63226         options = options || {};
63227
63228         if (Ext.isFunction(options)) {
63229             options = {
63230                 callback: options
63231             };
63232         }
63233
63234         Ext.applyIf(options, {
63235             groupers: me.groupers.items,
63236             page: me.currentPage,
63237             start: (me.currentPage - 1) * me.pageSize,
63238             limit: me.pageSize,
63239             addRecords: false
63240         });      
63241
63242         return me.callParent([options]);
63243     },
63244
63245     /**
63246      * @private
63247      * Called internally when a Proxy has completed a load request
63248      */
63249     onProxyLoad: function(operation) {
63250         var me = this,
63251             resultSet = operation.getResultSet(),
63252             records = operation.getRecords(),
63253             successful = operation.wasSuccessful();
63254
63255         if (resultSet) {
63256             me.totalCount = resultSet.total;
63257         }
63258
63259         if (successful) {
63260             me.loadRecords(records, operation);
63261         }
63262
63263         me.loading = false;
63264         me.fireEvent('load', me, records, successful);
63265
63266         //TODO: deprecate this event, it should always have been 'load' instead. 'load' is now documented, 'read' is not.
63267         //People are definitely using this so can't deprecate safely until 2.x
63268         me.fireEvent('read', me, records, operation.wasSuccessful());
63269
63270         //this is a callback that would have been passed to the 'read' function and is optional
63271         Ext.callback(operation.callback, operation.scope || me, [records, operation, successful]);
63272     },
63273     
63274     /**
63275      * Create any new records when a write is returned from the server.
63276      * @private
63277      * @param {Array} records The array of new records
63278      * @param {Ext.data.Operation} operation The operation that just completed
63279      * @param {Boolean} success True if the operation was successful
63280      */
63281     onCreateRecords: function(records, operation, success) {
63282         if (success) {
63283             var i = 0,
63284                 data = this.data,
63285                 snapshot = this.snapshot,
63286                 length = records.length,
63287                 originalRecords = operation.records,
63288                 record,
63289                 original,
63290                 index;
63291
63292             /**
63293              * Loop over each record returned from the server. Assume they are
63294              * returned in order of how they were sent. If we find a matching
63295              * record, replace it with the newly created one.
63296              */
63297             for (; i < length; ++i) {
63298                 record = records[i];
63299                 original = originalRecords[i];
63300                 if (original) {
63301                     index = data.indexOf(original);
63302                     if (index > -1) {
63303                         data.removeAt(index);
63304                         data.insert(index, record);
63305                     }
63306                     if (snapshot) {
63307                         index = snapshot.indexOf(original);
63308                         if (index > -1) {
63309                             snapshot.removeAt(index);
63310                             snapshot.insert(index, record);
63311                         }
63312                     }
63313                     record.phantom = false;
63314                     record.join(this);
63315                 }
63316             }
63317         }
63318     },
63319
63320     /**
63321      * Update any records when a write is returned from the server.
63322      * @private
63323      * @param {Array} records The array of updated records
63324      * @param {Ext.data.Operation} operation The operation that just completed
63325      * @param {Boolean} success True if the operation was successful
63326      */
63327     onUpdateRecords: function(records, operation, success){
63328         if (success) {
63329             var i = 0,
63330                 length = records.length,
63331                 data = this.data,
63332                 snapshot = this.snapshot,
63333                 record;
63334
63335             for (; i < length; ++i) {
63336                 record = records[i];
63337                 data.replace(record);
63338                 if (snapshot) {
63339                     snapshot.replace(record);
63340                 }
63341                 record.join(this);
63342             }
63343         }
63344     },
63345
63346     /**
63347      * Remove any records when a write is returned from the server.
63348      * @private
63349      * @param {Array} records The array of removed records
63350      * @param {Ext.data.Operation} operation The operation that just completed
63351      * @param {Boolean} success True if the operation was successful
63352      */
63353     onDestroyRecords: function(records, operation, success){
63354         if (success) {
63355             var me = this,
63356                 i = 0,
63357                 length = records.length,
63358                 data = me.data,
63359                 snapshot = me.snapshot,
63360                 record;
63361
63362             for (; i < length; ++i) {
63363                 record = records[i];
63364                 record.unjoin(me);
63365                 data.remove(record);
63366                 if (snapshot) {
63367                     snapshot.remove(record);
63368                 }
63369             }
63370             me.removed = [];
63371         }
63372     },
63373
63374     //inherit docs
63375     getNewRecords: function() {
63376         return this.data.filterBy(this.filterNew).items;
63377     },
63378
63379     //inherit docs
63380     getUpdatedRecords: function() {
63381         return this.data.filterBy(this.filterUpdated).items;
63382     },
63383
63384     /**
63385      * Filters the loaded set of records by a given set of filters.
63386      * @param {Mixed} filters The set of filters to apply to the data. These are stored internally on the store,
63387      * but the filtering itself is done on the Store's {@link Ext.util.MixedCollection MixedCollection}. See
63388      * MixedCollection's {@link Ext.util.MixedCollection#filter filter} method for filter syntax. Alternatively,
63389      * pass in a property string
63390      * @param {String} value Optional value to filter by (only if using a property string as the first argument)
63391      */
63392     filter: function(filters, value) {
63393         if (Ext.isString(filters)) {
63394             filters = {
63395                 property: filters,
63396                 value: value
63397             };
63398         }
63399
63400         var me = this,
63401             decoded = me.decodeFilters(filters),
63402             i = 0,
63403             doLocalSort = me.sortOnFilter && !me.remoteSort,
63404             length = decoded.length;
63405
63406         for (; i < length; i++) {
63407             me.filters.replace(decoded[i]);
63408         }
63409
63410         if (me.remoteFilter) {
63411             //the load function will pick up the new filters and request the filtered data from the proxy
63412             me.load();
63413         } else {
63414             /**
63415              * A pristine (unfiltered) collection of the records in this store. This is used to reinstate
63416              * records when a filter is removed or changed
63417              * @property snapshot
63418              * @type Ext.util.MixedCollection
63419              */
63420             if (me.filters.getCount()) {
63421                 me.snapshot = me.snapshot || me.data.clone();
63422                 me.data = me.data.filter(me.filters.items);
63423
63424                 if (doLocalSort) {
63425                     me.sort();
63426                 }
63427                 // fire datachanged event if it hasn't already been fired by doSort
63428                 if (!doLocalSort || me.sorters.length < 1) {
63429                     me.fireEvent('datachanged', me);
63430                 }
63431             }
63432         }
63433     },
63434
63435     /**
63436      * Revert to a view of the Record cache with no filtering applied.
63437      * @param {Boolean} suppressEvent If <tt>true</tt> the filter is cleared silently without firing the
63438      * {@link #datachanged} event.
63439      */
63440     clearFilter: function(suppressEvent) {
63441         var me = this;
63442
63443         me.filters.clear();
63444
63445         if (me.remoteFilter) {
63446             me.load();
63447         } else if (me.isFiltered()) {
63448             me.data = me.snapshot.clone();
63449             delete me.snapshot;
63450
63451             if (suppressEvent !== true) {
63452                 me.fireEvent('datachanged', me);
63453             }
63454         }
63455     },
63456
63457     /**
63458      * Returns true if this store is currently filtered
63459      * @return {Boolean}
63460      */
63461     isFiltered: function() {
63462         var snapshot = this.snapshot;
63463         return !! snapshot && snapshot !== this.data;
63464     },
63465
63466     /**
63467      * Filter by a function. The specified function will be called for each
63468      * Record in this Store. If the function returns <tt>true</tt> the Record is included,
63469      * otherwise it is filtered out.
63470      * @param {Function} fn The function to be called. It will be passed the following parameters:<ul>
63471      * <li><b>record</b> : Ext.data.Model<p class="sub-desc">The {@link Ext.data.Model record}
63472      * to test for filtering. Access field values using {@link Ext.data.Model#get}.</p></li>
63473      * <li><b>id</b> : Object<p class="sub-desc">The ID of the Record passed.</p></li>
63474      * </ul>
63475      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this Store.
63476      */
63477     filterBy: function(fn, scope) {
63478         var me = this;
63479
63480         me.snapshot = me.snapshot || me.data.clone();
63481         me.data = me.queryBy(fn, scope || me);
63482         me.fireEvent('datachanged', me);
63483     },
63484
63485     /**
63486      * Query the cached records in this Store using a filtering function. The specified function
63487      * will be called with each record in this Store. If the function returns <tt>true</tt> the record is
63488      * included in the results.
63489      * @param {Function} fn The function to be called. It will be passed the following parameters:<ul>
63490      * <li><b>record</b> : Ext.data.Model<p class="sub-desc">The {@link Ext.data.Model record}
63491      * to test for filtering. Access field values using {@link Ext.data.Model#get}.</p></li>
63492      * <li><b>id</b> : Object<p class="sub-desc">The ID of the Record passed.</p></li>
63493      * </ul>
63494      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this Store.
63495      * @return {MixedCollection} Returns an Ext.util.MixedCollection of the matched records
63496      **/
63497     queryBy: function(fn, scope) {
63498         var me = this,
63499         data = me.snapshot || me.data;
63500         return data.filterBy(fn, scope || me);
63501     },
63502
63503     /**
63504      * Loads an array of data straight into the Store
63505      * @param {Array} data Array of data to load. Any non-model instances will be cast into model instances first
63506      * @param {Boolean} append True to add the records to the existing records in the store, false to remove the old ones first
63507      */
63508     loadData: function(data, append) {
63509         var model = this.model,
63510             length = data.length,
63511             i,
63512             record;
63513
63514         //make sure each data element is an Ext.data.Model instance
63515         for (i = 0; i < length; i++) {
63516             record = data[i];
63517
63518             if (! (record instanceof Ext.data.Model)) {
63519                 data[i] = Ext.ModelManager.create(record, model);
63520             }
63521         }
63522
63523         this.loadRecords(data, {addRecords: append});
63524     },
63525
63526     /**
63527      * Loads an array of {@Ext.data.Model model} instances into the store, fires the datachanged event. This should only usually
63528      * be called internally when loading from the {@link Ext.data.proxy.Proxy Proxy}, when adding records manually use {@link #add} instead
63529      * @param {Array} records The array of records to load
63530      * @param {Object} options {addRecords: true} to add these records to the existing records, false to remove the Store's existing records first
63531      */
63532     loadRecords: function(records, options) {
63533         var me     = this,
63534             i      = 0,
63535             length = records.length;
63536
63537         options = options || {};
63538
63539
63540         if (!options.addRecords) {
63541             delete me.snapshot;
63542             me.data.clear();
63543         }
63544
63545         me.data.addAll(records);
63546
63547         //FIXME: this is not a good solution. Ed Spencer is totally responsible for this and should be forced to fix it immediately.
63548         for (; i < length; i++) {
63549             if (options.start !== undefined) {
63550                 records[i].index = options.start + i;
63551
63552             }
63553             records[i].join(me);
63554         }
63555
63556         /*
63557          * this rather inelegant suspension and resumption of events is required because both the filter and sort functions
63558          * fire an additional datachanged event, which is not wanted. Ideally we would do this a different way. The first
63559          * datachanged event is fired by the call to this.add, above.
63560          */
63561         me.suspendEvents();
63562
63563         if (me.filterOnLoad && !me.remoteFilter) {
63564             me.filter();
63565         }
63566
63567         if (me.sortOnLoad && !me.remoteSort) {
63568             me.sort();
63569         }
63570
63571         me.resumeEvents();
63572         me.fireEvent('datachanged', me, records);
63573     },
63574
63575     // PAGING METHODS
63576     /**
63577      * Loads a given 'page' of data by setting the start and limit values appropriately. Internally this just causes a normal
63578      * load operation, passing in calculated 'start' and 'limit' params
63579      * @param {Number} page The number of the page to load
63580      */
63581     loadPage: function(page) {
63582         var me = this;
63583
63584         me.currentPage = page;
63585
63586         me.read({
63587             page: page,
63588             start: (page - 1) * me.pageSize,
63589             limit: me.pageSize,
63590             addRecords: !me.clearOnPageLoad
63591         });
63592     },
63593
63594     /**
63595      * Loads the next 'page' in the current data set
63596      */
63597     nextPage: function() {
63598         this.loadPage(this.currentPage + 1);
63599     },
63600
63601     /**
63602      * Loads the previous 'page' in the current data set
63603      */
63604     previousPage: function() {
63605         this.loadPage(this.currentPage - 1);
63606     },
63607
63608     // private
63609     clearData: function() {
63610         this.data.each(function(record) {
63611             record.unjoin();
63612         });
63613
63614         this.data.clear();
63615     },
63616     
63617     // Buffering
63618     /**
63619      * Prefetches data the Store using its configured {@link #proxy}.
63620      * @param {Object} options Optional config object, passed into the Ext.data.Operation object before loading.
63621      * See {@link #load}
63622      */
63623     prefetch: function(options) {
63624         var me = this,
63625             operation,
63626             requestId = me.getRequestId();
63627
63628         options = options || {};
63629
63630         Ext.applyIf(options, {
63631             action : 'read',
63632             filters: me.filters.items,
63633             sorters: me.sorters.items,
63634             requestId: requestId
63635         });
63636         me.pendingRequests.push(requestId);
63637
63638         operation = Ext.create('Ext.data.Operation', options);
63639
63640         // HACK to implement loadMask support.
63641         //if (operation.blocking) {
63642         //    me.fireEvent('beforeload', me, operation);
63643         //}
63644         if (me.fireEvent('beforeprefetch', me, operation) !== false) {
63645             me.loading = true;
63646             me.proxy.read(operation, me.onProxyPrefetch, me);
63647         }
63648         
63649         return me;
63650     },
63651     
63652     /**
63653      * Prefetches a page of data.
63654      * @param {Number} page The page to prefetch
63655      * @param {Object} options Optional config object, passed into the Ext.data.Operation object before loading.
63656      * See {@link #load}
63657      * @param
63658      */
63659     prefetchPage: function(page, options) {
63660         var me = this,
63661             pageSize = me.pageSize,
63662             start = (page - 1) * me.pageSize,
63663             end = start + pageSize;
63664         
63665         // Currently not requesting this page and range isn't already satisified 
63666         if (Ext.Array.indexOf(me.pagesRequested, page) === -1 && !me.rangeSatisfied(start, end)) {
63667             options = options || {};
63668             me.pagesRequested.push(page);
63669             Ext.applyIf(options, {
63670                 page : page,
63671                 start: start,
63672                 limit: pageSize,
63673                 callback: me.onWaitForGuarantee,
63674                 scope: me
63675             });
63676             
63677             me.prefetch(options);
63678         }
63679         
63680     },
63681     
63682     /**
63683      * Returns a unique requestId to track requests.
63684      * @private
63685      */
63686     getRequestId: function() {
63687         this.requestSeed = this.requestSeed || 1;
63688         return this.requestSeed++;
63689     },
63690     
63691     /**
63692      * Handles a success pre-fetch
63693      * @private
63694      * @param {Ext.data.Operation} operation The operation that completed
63695      */
63696     onProxyPrefetch: function(operation) {
63697         var me         = this,
63698             resultSet  = operation.getResultSet(),
63699             records    = operation.getRecords(),
63700             
63701             successful = operation.wasSuccessful();
63702         
63703         if (resultSet) {
63704             me.totalCount = resultSet.total;
63705             me.fireEvent('totalcountchange', me.totalCount);
63706         }
63707         
63708         if (successful) {
63709             me.cacheRecords(records, operation);
63710         }
63711         Ext.Array.remove(me.pendingRequests, operation.requestId);
63712         if (operation.page) {
63713             Ext.Array.remove(me.pagesRequested, operation.page);
63714         }
63715         
63716         me.loading = false;
63717         me.fireEvent('prefetch', me, records, successful, operation);
63718         
63719         // HACK to support loadMask
63720         if (operation.blocking) {
63721             me.fireEvent('load', me, records, successful);
63722         }
63723
63724         //this is a callback that would have been passed to the 'read' function and is optional
63725         Ext.callback(operation.callback, operation.scope || me, [records, operation, successful]);
63726     },
63727     
63728     /**
63729      * Caches the records in the prefetch and stripes them with their server-side
63730      * index.
63731      * @private
63732      * @param {Array} records The records to cache
63733      * @param {Ext.data.Operation} The associated operation
63734      */
63735     cacheRecords: function(records, operation) {
63736         var me     = this,
63737             i      = 0,
63738             length = records.length,
63739             start  = operation ? operation.start : 0;
63740         
63741         if (!Ext.isDefined(me.totalCount)) {
63742             me.totalCount = records.length;
63743             me.fireEvent('totalcountchange', me.totalCount);
63744         }
63745         
63746         for (; i < length; i++) {
63747             // this is the true index, not the viewIndex
63748             records[i].index = start + i;
63749         }
63750         
63751         me.prefetchData.addAll(records);
63752         if (me.purgePageCount) {
63753             me.purgeRecords();
63754         }
63755         
63756     },
63757     
63758     
63759     /**
63760      * Purge the least recently used records in the prefetch if the purgeCount
63761      * has been exceeded.
63762      */
63763     purgeRecords: function() {
63764         var me = this,
63765             prefetchCount = me.prefetchData.getCount(),
63766             purgeCount = me.purgePageCount * me.pageSize,
63767             numRecordsToPurge = prefetchCount - purgeCount - 1,
63768             i = 0;
63769
63770         for (; i <= numRecordsToPurge; i++) {
63771             me.prefetchData.removeAt(0);
63772         }
63773     },
63774     
63775     /**
63776      * Determines if the range has already been satisfied in the prefetchData.
63777      * @private
63778      * @param {Number} start The start index
63779      * @param {Number} end The end index in the range
63780      */
63781     rangeSatisfied: function(start, end) {
63782         var me = this,
63783             i = start,
63784             satisfied = true;
63785
63786         for (; i < end; i++) {
63787             if (!me.prefetchData.getByKey(i)) {
63788                 satisfied = false;
63789                 if (end - i > me.pageSize) {
63790                     Ext.Error.raise("A single page prefetch could never satisfy this request.");
63791                 }
63792                 break;
63793             }
63794         }
63795         return satisfied;
63796     },
63797     
63798     /**
63799      * Determines the page from a record index
63800      * @param {Number} index The record index
63801      * @return {Number} The page the record belongs to
63802      */
63803     getPageFromRecordIndex: function(index) {
63804         return Math.floor(index / this.pageSize) + 1;
63805     },
63806     
63807     /**
63808      * Handles a guaranteed range being loaded
63809      * @private
63810      */
63811     onGuaranteedRange: function() {
63812         var me = this,
63813             totalCount = me.getTotalCount(),
63814             start = me.requestStart,
63815             end = ((totalCount - 1) < me.requestEnd) ? totalCount - 1 : me.requestEnd,
63816             range = [],
63817             record,
63818             i = start;
63819             
63820         if (start > end) {
63821             Ext.Error.raise("Start (" + start + ") was greater than end (" + end + ")");
63822         }
63823         
63824         if (start !== me.guaranteedStart && end !== me.guaranteedEnd) {
63825             me.guaranteedStart = start;
63826             me.guaranteedEnd = end;
63827             
63828             for (; i <= end; i++) {
63829                 record = me.prefetchData.getByKey(i);
63830                 if (!record) {
63831                     Ext.Error.raise("Record was not found and store said it was guaranteed");
63832                 }
63833                 range.push(record);
63834             }
63835             me.fireEvent('guaranteedrange', range, start, end);
63836             if (me.cb) {
63837                 me.cb.call(me.scope || me, range);
63838             }
63839         }
63840         
63841         me.unmask();
63842     },
63843     
63844     // hack to support loadmask
63845     mask: function() {
63846         this.masked = true;
63847         this.fireEvent('beforeload');
63848     },
63849     
63850     // hack to support loadmask
63851     unmask: function() {
63852         if (this.masked) {
63853             this.fireEvent('load');
63854         }
63855     },
63856     
63857     /**
63858      * Returns the number of pending requests out.
63859      */
63860     hasPendingRequests: function() {
63861         return this.pendingRequests.length;
63862     },
63863     
63864     
63865     // wait until all requests finish, until guaranteeing the range.
63866     onWaitForGuarantee: function() {
63867         if (!this.hasPendingRequests()) {
63868             this.onGuaranteedRange();
63869         }
63870     },
63871     
63872     /**
63873      * Guarantee a specific range, this will load the store with a range (that
63874      * must be the pageSize or smaller) and take care of any loading that may
63875      * be necessary.
63876      */
63877     guaranteeRange: function(start, end, cb, scope) {
63878         if (start && end) {
63879             if (end - start > this.pageSize) {
63880                 Ext.Error.raise({
63881                     start: start,
63882                     end: end,
63883                     pageSize: this.pageSize,
63884                     msg: "Requested a bigger range than the specified pageSize"
63885                 });
63886             }
63887         }
63888         
63889         end = (end > this.totalCount) ? this.totalCount - 1 : end;
63890         
63891         var me = this,
63892             i = start,
63893             prefetchData = me.prefetchData,
63894             range = [],
63895             startLoaded = !!prefetchData.getByKey(start),
63896             endLoaded = !!prefetchData.getByKey(end),
63897             startPage = me.getPageFromRecordIndex(start),
63898             endPage = me.getPageFromRecordIndex(end);
63899             
63900         me.cb = cb;
63901         me.scope = scope;
63902
63903         me.requestStart = start;
63904         me.requestEnd = end;
63905         // neither beginning or end are loaded
63906         if (!startLoaded || !endLoaded) {
63907             // same page, lets load it
63908             if (startPage === endPage) {
63909                 me.mask();
63910                 me.prefetchPage(startPage, {
63911                     //blocking: true,
63912                     callback: me.onWaitForGuarantee,
63913                     scope: me
63914                 });
63915             // need to load two pages
63916             } else {
63917                 me.mask();
63918                 me.prefetchPage(startPage, {
63919                     //blocking: true,
63920                     callback: me.onWaitForGuarantee,
63921                     scope: me
63922                 });
63923                 me.prefetchPage(endPage, {
63924                     //blocking: true,
63925                     callback: me.onWaitForGuarantee,
63926                     scope: me
63927                 });
63928             }
63929         // Request was already satisfied via the prefetch
63930         } else {
63931             me.onGuaranteedRange();
63932         }
63933     },
63934     
63935     // because prefetchData is stored by index
63936     // this invalidates all of the prefetchedData
63937     sort: function() {
63938         var me = this,
63939             prefetchData = me.prefetchData,
63940             sorters,
63941             start,
63942             end,
63943             range;
63944             
63945         if (me.buffered) {
63946             if (me.remoteSort) {
63947                 prefetchData.clear();
63948                 me.callParent(arguments);
63949             } else {
63950                 sorters = me.getSorters();
63951                 start = me.guaranteedStart;
63952                 end = me.guaranteedEnd;
63953                 range;
63954                 
63955                 if (sorters.length) {
63956                     prefetchData.sort(sorters);
63957                     range = prefetchData.getRange();
63958                     prefetchData.clear();
63959                     me.cacheRecords(range);
63960                     delete me.guaranteedStart;
63961                     delete me.guaranteedEnd;
63962                     me.guaranteeRange(start, end);
63963                 }
63964                 me.callParent(arguments);
63965             }
63966         } else {
63967             me.callParent(arguments);
63968         }
63969     },
63970
63971     // overriden to provide striping of the indexes as sorting occurs.
63972     // this cannot be done inside of sort because datachanged has already
63973     // fired and will trigger a repaint of the bound view.
63974     doSort: function(sorterFn) {
63975         var me = this;
63976         if (me.remoteSort) {
63977             //the load function will pick up the new sorters and request the sorted data from the proxy
63978             me.load();
63979         } else {
63980             me.data.sortBy(sorterFn);
63981             if (!me.buffered) {
63982                 var range = me.getRange(),
63983                     ln = range.length,
63984                     i  = 0;
63985                 for (; i < ln; i++) {
63986                     range[i].index = i;
63987                 }
63988             }
63989             me.fireEvent('datachanged', me);
63990         }
63991     },
63992     
63993     /**
63994      * Finds the index of the first matching Record in this store by a specific field value.
63995      * @param {String} fieldName The name of the Record field to test.
63996      * @param {String/RegExp} value Either a string that the field value
63997      * should begin with, or a RegExp to test against the field.
63998      * @param {Number} startIndex (optional) The index to start searching at
63999      * @param {Boolean} anyMatch (optional) True to match any part of the string, not just the beginning
64000      * @param {Boolean} caseSensitive (optional) True for case sensitive comparison
64001      * @param {Boolean} exactMatch True to force exact match (^ and $ characters added to the regex). Defaults to false.
64002      * @return {Number} The matched index or -1
64003      */
64004     find: function(property, value, start, anyMatch, caseSensitive, exactMatch) {
64005         var fn = this.createFilterFn(property, value, anyMatch, caseSensitive, exactMatch);
64006         return fn ? this.data.findIndexBy(fn, null, start) : -1;
64007     },
64008
64009     /**
64010      * Finds the first matching Record in this store by a specific field value.
64011      * @param {String} fieldName The name of the Record field to test.
64012      * @param {String/RegExp} value Either a string that the field value
64013      * should begin with, or a RegExp to test against the field.
64014      * @param {Number} startIndex (optional) The index to start searching at
64015      * @param {Boolean} anyMatch (optional) True to match any part of the string, not just the beginning
64016      * @param {Boolean} caseSensitive (optional) True for case sensitive comparison
64017      * @param {Boolean} exactMatch True to force exact match (^ and $ characters added to the regex). Defaults to false.
64018      * @return {Ext.data.Model} The matched record or null
64019      */
64020     findRecord: function() {
64021         var me = this,
64022             index = me.find.apply(me, arguments);
64023         return index !== -1 ? me.getAt(index) : null;
64024     },
64025
64026     /**
64027      * @private
64028      * Returns a filter function used to test a the given property's value. Defers most of the work to
64029      * Ext.util.MixedCollection's createValueMatcher function
64030      * @param {String} property The property to create the filter function for
64031      * @param {String/RegExp} value The string/regex to compare the property value to
64032      * @param {Boolean} anyMatch True if we don't care if the filter value is not the full value (defaults to false)
64033      * @param {Boolean} caseSensitive True to create a case-sensitive regex (defaults to false)
64034      * @param {Boolean} exactMatch True to force exact match (^ and $ characters added to the regex). Defaults to false.
64035      * Ignored if anyMatch is true.
64036      */
64037     createFilterFn: function(property, value, anyMatch, caseSensitive, exactMatch) {
64038         if (Ext.isEmpty(value)) {
64039             return false;
64040         }
64041         value = this.data.createValueMatcher(value, anyMatch, caseSensitive, exactMatch);
64042         return function(r) {
64043             return value.test(r.data[property]);
64044         };
64045     },
64046
64047     /**
64048      * Finds the index of the first matching Record in this store by a specific field value.
64049      * @param {String} fieldName The name of the Record field to test.
64050      * @param {Mixed} value The value to match the field against.
64051      * @param {Number} startIndex (optional) The index to start searching at
64052      * @return {Number} The matched index or -1
64053      */
64054     findExact: function(property, value, start) {
64055         return this.data.findIndexBy(function(rec) {
64056             return rec.get(property) === value;
64057         },
64058         this, start);
64059     },
64060
64061     /**
64062      * Find the index of the first matching Record in this Store by a function.
64063      * If the function returns <tt>true</tt> it is considered a match.
64064      * @param {Function} fn The function to be called. It will be passed the following parameters:<ul>
64065      * <li><b>record</b> : Ext.data.Model<p class="sub-desc">The {@link Ext.data.Model record}
64066      * to test for filtering. Access field values using {@link Ext.data.Model#get}.</p></li>
64067      * <li><b>id</b> : Object<p class="sub-desc">The ID of the Record passed.</p></li>
64068      * </ul>
64069      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this Store.
64070      * @param {Number} startIndex (optional) The index to start searching at
64071      * @return {Number} The matched index or -1
64072      */
64073     findBy: function(fn, scope, start) {
64074         return this.data.findIndexBy(fn, scope, start);
64075     },
64076
64077     /**
64078      * Collects unique values for a particular dataIndex from this store.
64079      * @param {String} dataIndex The property to collect
64080      * @param {Boolean} allowNull (optional) Pass true to allow null, undefined or empty string values
64081      * @param {Boolean} bypassFilter (optional) Pass true to collect from all records, even ones which are filtered
64082      * @return {Array} An array of the unique values
64083      **/
64084     collect: function(dataIndex, allowNull, bypassFilter) {
64085         var me = this,
64086             data = (bypassFilter === true && me.snapshot) ? me.snapshot: me.data;
64087
64088         return data.collect(dataIndex, 'data', allowNull);
64089     },
64090
64091     /**
64092      * Gets the number of cached records.
64093      * <p>If using paging, this may not be the total size of the dataset. If the data object
64094      * used by the Reader contains the dataset size, then the {@link #getTotalCount} function returns
64095      * the dataset size.  <b>Note</b>: see the Important note in {@link #load}.</p>
64096      * @return {Number} The number of Records in the Store's cache.
64097      */
64098     getCount: function() {
64099         return this.data.length || 0;
64100     },
64101
64102     /**
64103      * Returns the total number of {@link Ext.data.Model Model} instances that the {@link Ext.data.proxy.Proxy Proxy}
64104      * indicates exist. This will usually differ from {@link #getCount} when using paging - getCount returns the
64105      * number of records loaded into the Store at the moment, getTotalCount returns the number of records that
64106      * could be loaded into the Store if the Store contained all data
64107      * @return {Number} The total number of Model instances available via the Proxy
64108      */
64109     getTotalCount: function() {
64110         return this.totalCount;
64111     },
64112
64113     /**
64114      * Get the Record at the specified index.
64115      * @param {Number} index The index of the Record to find.
64116      * @return {Ext.data.Model} The Record at the passed index. Returns undefined if not found.
64117      */
64118     getAt: function(index) {
64119         return this.data.getAt(index);
64120     },
64121
64122     /**
64123      * Returns a range of Records between specified indices.
64124      * @param {Number} startIndex (optional) The starting index (defaults to 0)
64125      * @param {Number} endIndex (optional) The ending index (defaults to the last Record in the Store)
64126      * @return {Ext.data.Model[]} An array of Records
64127      */
64128     getRange: function(start, end) {
64129         return this.data.getRange(start, end);
64130     },
64131
64132     /**
64133      * Get the Record with the specified id.
64134      * @param {String} id The id of the Record to find.
64135      * @return {Ext.data.Model} The Record with the passed id. Returns undefined if not found.
64136      */
64137     getById: function(id) {
64138         return (this.snapshot || this.data).findBy(function(record) {
64139             return record.getId() === id;
64140         });
64141     },
64142
64143     /**
64144      * Get the index within the cache of the passed Record.
64145      * @param {Ext.data.Model} record The Ext.data.Model object to find.
64146      * @return {Number} The index of the passed Record. Returns -1 if not found.
64147      */
64148     indexOf: function(record) {
64149         return this.data.indexOf(record);
64150     },
64151
64152
64153     /**
64154      * Get the index within the entire dataset. From 0 to the totalCount.
64155      * @param {Ext.data.Model} record The Ext.data.Model object to find.
64156      * @return {Number} The index of the passed Record. Returns -1 if not found.
64157      */
64158     indexOfTotal: function(record) {
64159         return record.index || this.indexOf(record);
64160     },
64161
64162     /**
64163      * Get the index within the cache of the Record with the passed id.
64164      * @param {String} id The id of the Record to find.
64165      * @return {Number} The index of the Record. Returns -1 if not found.
64166      */
64167     indexOfId: function(id) {
64168         return this.data.indexOfKey(id);
64169     },
64170         
64171     /**
64172      * Remove all items from the store.
64173      * @param {Boolean} silent Prevent the `clear` event from being fired.
64174      */
64175     removeAll: function(silent) {
64176         var me = this;
64177
64178         me.clearData();
64179         if (me.snapshot) {
64180             me.snapshot.clear();
64181         }
64182         if (silent !== true) {
64183             me.fireEvent('clear', me);
64184         }
64185     },
64186
64187     /*
64188      * Aggregation methods
64189      */
64190
64191     /**
64192      * Convenience function for getting the first model instance in the store
64193      * @param {Boolean} grouped (Optional) True to perform the operation for each group
64194      * in the store. The value returned will be an object literal with the key being the group
64195      * name and the first record being the value. The grouped parameter is only honored if
64196      * the store has a groupField.
64197      * @return {Ext.data.Model/undefined} The first model instance in the store, or undefined
64198      */
64199     first: function(grouped) {
64200         var me = this;
64201
64202         if (grouped && me.isGrouped()) {
64203             return me.aggregate(function(records) {
64204                 return records.length ? records[0] : undefined;
64205             }, me, true);
64206         } else {
64207             return me.data.first();
64208         }
64209     },
64210
64211     /**
64212      * Convenience function for getting the last model instance in the store
64213      * @param {Boolean} grouped (Optional) True to perform the operation for each group
64214      * in the store. The value returned will be an object literal with the key being the group
64215      * name and the last record being the value. The grouped parameter is only honored if
64216      * the store has a groupField.
64217      * @return {Ext.data.Model/undefined} The last model instance in the store, or undefined
64218      */
64219     last: function(grouped) {
64220         var me = this;
64221
64222         if (grouped && me.isGrouped()) {
64223             return me.aggregate(function(records) {
64224                 var len = records.length;
64225                 return len ? records[len - 1] : undefined;
64226             }, me, true);
64227         } else {
64228             return me.data.last();
64229         }
64230     },
64231
64232     /**
64233      * Sums the value of <tt>property</tt> for each {@link Ext.data.Model record} between <tt>start</tt>
64234      * and <tt>end</tt> and returns the result.
64235      * @param {String} field A field in each record
64236      * @param {Boolean} grouped (Optional) True to perform the operation for each group
64237      * in the store. The value returned will be an object literal with the key being the group
64238      * name and the sum for that group being the value. The grouped parameter is only honored if
64239      * the store has a groupField.
64240      * @return {Number} The sum
64241      */
64242     sum: function(field, grouped) {
64243         var me = this;
64244
64245         if (grouped && me.isGrouped()) {
64246             return me.aggregate(me.getSum, me, true, [field]);
64247         } else {
64248             return me.getSum(me.data.items, field);
64249         }
64250     },
64251
64252     // @private, see sum
64253     getSum: function(records, field) {
64254         var total = 0,
64255             i = 0,
64256             len = records.length;
64257
64258         for (; i < len; ++i) {
64259             total += records[i].get(field);
64260         }
64261
64262         return total;
64263     },
64264
64265     /**
64266      * Gets the count of items in the store.
64267      * @param {Boolean} grouped (Optional) True to perform the operation for each group
64268      * in the store. The value returned will be an object literal with the key being the group
64269      * name and the count for each group being the value. The grouped parameter is only honored if
64270      * the store has a groupField.
64271      * @return {Number} the count
64272      */
64273     count: function(grouped) {
64274         var me = this;
64275
64276         if (grouped && me.isGrouped()) {
64277             return me.aggregate(function(records) {
64278                 return records.length;
64279             }, me, true);
64280         } else {
64281             return me.getCount();
64282         }
64283     },
64284
64285     /**
64286      * Gets the minimum value in the store.
64287      * @param {String} field The field in each record
64288      * @param {Boolean} grouped (Optional) True to perform the operation for each group
64289      * in the store. The value returned will be an object literal with the key being the group
64290      * name and the minimum in the group being the value. The grouped parameter is only honored if
64291      * the store has a groupField.
64292      * @return {Mixed/undefined} The minimum value, if no items exist, undefined.
64293      */
64294     min: function(field, grouped) {
64295         var me = this;
64296
64297         if (grouped && me.isGrouped()) {
64298             return me.aggregate(me.getMin, me, true, [field]);
64299         } else {
64300             return me.getMin(me.data.items, field);
64301         }
64302     },
64303
64304     // @private, see min
64305     getMin: function(records, field){
64306         var i = 1,
64307             len = records.length,
64308             value, min;
64309
64310         if (len > 0) {
64311             min = records[0].get(field);
64312         }
64313
64314         for (; i < len; ++i) {
64315             value = records[i].get(field);
64316             if (value < min) {
64317                 min = value;
64318             }
64319         }
64320         return min;
64321     },
64322
64323     /**
64324      * Gets the maximum value in the store.
64325      * @param {String} field The field in each record
64326      * @param {Boolean} grouped (Optional) True to perform the operation for each group
64327      * in the store. The value returned will be an object literal with the key being the group
64328      * name and the maximum in the group being the value. The grouped parameter is only honored if
64329      * the store has a groupField.
64330      * @return {Mixed/undefined} The maximum value, if no items exist, undefined.
64331      */
64332     max: function(field, grouped) {
64333         var me = this;
64334
64335         if (grouped && me.isGrouped()) {
64336             return me.aggregate(me.getMax, me, true, [field]);
64337         } else {
64338             return me.getMax(me.data.items, field);
64339         }
64340     },
64341
64342     // @private, see max
64343     getMax: function(records, field) {
64344         var i = 1,
64345             len = records.length,
64346             value,
64347             max;
64348
64349         if (len > 0) {
64350             max = records[0].get(field);
64351         }
64352
64353         for (; i < len; ++i) {
64354             value = records[i].get(field);
64355             if (value > max) {
64356                 max = value;
64357             }
64358         }
64359         return max;
64360     },
64361
64362     /**
64363      * Gets the average value in the store.
64364      * @param {String} field The field in each record
64365      * @param {Boolean} grouped (Optional) True to perform the operation for each group
64366      * in the store. The value returned will be an object literal with the key being the group
64367      * name and the group average being the value. The grouped parameter is only honored if
64368      * the store has a groupField.
64369      * @return {Mixed/undefined} The average value, if no items exist, 0.
64370      */
64371     average: function(field, grouped) {
64372         var me = this;
64373         if (grouped && me.isGrouped()) {
64374             return me.aggregate(me.getAverage, me, true, [field]);
64375         } else {
64376             return me.getAverage(me.data.items, field);
64377         }
64378     },
64379
64380     // @private, see average
64381     getAverage: function(records, field) {
64382         var i = 0,
64383             len = records.length,
64384             sum = 0;
64385
64386         if (records.length > 0) {
64387             for (; i < len; ++i) {
64388                 sum += records[i].get(field);
64389             }
64390             return sum / len;
64391         }
64392         return 0;
64393     },
64394
64395     /**
64396      * Runs the aggregate function for all the records in the store.
64397      * @param {Function} fn The function to execute. The function is called with a single parameter,
64398      * an array of records for that group.
64399      * @param {Object} scope (optional) The scope to execute the function in. Defaults to the store.
64400      * @param {Boolean} grouped (Optional) True to perform the operation for each group
64401      * in the store. The value returned will be an object literal with the key being the group
64402      * name and the group average being the value. The grouped parameter is only honored if
64403      * the store has a groupField.
64404      * @param {Array} args (optional) Any arguments to append to the function call
64405      * @return {Object} An object literal with the group names and their appropriate values.
64406      */
64407     aggregate: function(fn, scope, grouped, args) {
64408         args = args || [];
64409         if (grouped && this.isGrouped()) {
64410             var groups = this.getGroups(),
64411                 i = 0,
64412                 len = groups.length,
64413                 out = {},
64414                 group;
64415
64416             for (; i < len; ++i) {
64417                 group = groups[i];
64418                 out[group.name] = fn.apply(scope || this, [group.children].concat(args));
64419             }
64420             return out;
64421         } else {
64422             return fn.apply(scope || this, [this.data.items].concat(args));
64423         }
64424     }
64425 });
64426
64427 /**
64428  * @author Ed Spencer
64429  * @class Ext.data.JsonStore
64430  * @extends Ext.data.Store
64431  * @ignore
64432  *
64433  * <p>Small helper class to make creating {@link Ext.data.Store}s from JSON data easier.
64434  * A JsonStore will be automatically configured with a {@link Ext.data.reader.Json}.</p>
64435  *
64436  * <p>A store configuration would be something like:</p>
64437  *
64438 <pre><code>
64439 var store = new Ext.data.JsonStore({
64440     // store configs
64441     autoDestroy: true,
64442     storeId: 'myStore'
64443
64444     proxy: {
64445         type: 'ajax',
64446         url: 'get-images.php',
64447         reader: {
64448             type: 'json',
64449             root: 'images',
64450             idProperty: 'name'
64451         }
64452     },
64453
64454     //alternatively, a {@link Ext.data.Model} name can be given (see {@link Ext.data.Store} for an example)
64455     fields: ['name', 'url', {name:'size', type: 'float'}, {name:'lastmod', type:'date'}]
64456 });
64457 </code></pre>
64458  *
64459  * <p>This store is configured to consume a returned object of the form:<pre><code>
64460 {
64461     images: [
64462         {name: 'Image one', url:'/GetImage.php?id=1', size:46.5, lastmod: new Date(2007, 10, 29)},
64463         {name: 'Image Two', url:'/GetImage.php?id=2', size:43.2, lastmod: new Date(2007, 10, 30)}
64464     ]
64465 }
64466 </code></pre>
64467  *
64468  * <p>An object literal of this form could also be used as the {@link #data} config option.</p>
64469  *
64470  * @constructor
64471  * @param {Object} config
64472  * @xtype jsonstore
64473  */
64474 Ext.define('Ext.data.JsonStore',  {
64475     extend: 'Ext.data.Store',
64476     alias: 'store.json',
64477
64478     /**
64479      * @cfg {Ext.data.DataReader} reader @hide
64480      */
64481     constructor: function(config) {
64482         config = config || {};
64483
64484         Ext.applyIf(config, {
64485             proxy: {
64486                 type  : 'ajax',
64487                 reader: 'json',
64488                 writer: 'json'
64489             }
64490         });
64491
64492         this.callParent([config]);
64493     }
64494 });
64495
64496 /**
64497  * @class Ext.chart.axis.Time
64498  * @extends Ext.chart.axis.Axis
64499  *
64500  * A type of axis whose units are measured in time values. Use this axis
64501  * for listing dates that you will want to group or dynamically change.
64502  * If you just want to display dates as categories then use the
64503  * Category class for axis instead.
64504  *
64505  * For example:
64506  *
64507   <pre><code>
64508     axes: [{
64509         type: 'Time',
64510         position: 'bottom',
64511         fields: 'date',
64512         title: 'Day',
64513         dateFormat: 'M d',
64514         groupBy: 'year,month,day',
64515         aggregateOp: 'sum',
64516
64517         constrain: true,
64518         fromDate: new Date('1/1/11'),
64519         toDate: new Date('1/7/11')
64520     }]
64521   </code></pre>
64522  *
64523  * In this example we're creating a time axis that has as title <em>Day</em>.
64524  * The field the axis is bound to is <em>date</em>.
64525  * The date format to use to display the text for the axis labels is <em>M d</em>
64526  * which is a three letter month abbreviation followed by the day number.
64527  * The time axis will show values for dates betwee <em>fromDate</em> and <em>toDate</em>.
64528  * Since <em>constrain</em> is set to true all other values for other dates not between
64529  * the fromDate and toDate will not be displayed.
64530  * 
64531  * @constructor
64532  */
64533 Ext.define('Ext.chart.axis.Time', {
64534
64535     /* Begin Definitions */
64536
64537     extend: 'Ext.chart.axis.Category',
64538
64539     alternateClassName: 'Ext.chart.TimeAxis',
64540
64541     alias: 'axis.time',
64542
64543     requires: ['Ext.data.Store', 'Ext.data.JsonStore'],
64544
64545     /* End Definitions */
64546
64547      /**
64548       * The minimum value drawn by the axis. If not set explicitly, the axis
64549       * minimum will be calculated automatically.
64550       * @property calculateByLabelSize
64551       * @type Boolean
64552       */
64553     calculateByLabelSize: true,
64554     
64555      /**
64556      * Indicates the format the date will be rendered on. 
64557      * For example: 'M d' will render the dates as 'Jan 30', etc.
64558       *
64559      * @property dateFormat
64560      * @type {String|Boolean}
64561       */
64562     dateFormat: false,
64563     
64564      /**
64565      * Indicates the time unit to use for each step. Can be 'day', 'month', 'year' or a comma-separated combination of all of them.
64566      * Default's 'year,month,day'.
64567      *
64568      * @property timeUnit
64569      * @type {String}
64570      */
64571     groupBy: 'year,month,day',
64572     
64573     /**
64574      * Aggregation operation when grouping. Possible options are 'sum', 'avg', 'max', 'min'. Default's 'sum'.
64575      * 
64576      * @property aggregateOp
64577      * @type {String}
64578       */
64579     aggregateOp: 'sum',
64580     
64581     /**
64582      * The starting date for the time axis.
64583      * @property fromDate
64584      * @type Date
64585      */
64586     fromDate: false,
64587     
64588     /**
64589      * The ending date for the time axis.
64590      * @property toDate
64591      * @type Date
64592      */
64593     toDate: false,
64594     
64595     /**
64596      * 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.).
64597      * Default's [Ext.Date.DAY, 1].
64598      * 
64599      * @property step 
64600      * @type Array
64601      */
64602     step: [Ext.Date.DAY, 1],
64603     
64604     /**
64605      * If true, the values of the chart will be rendered only if they belong between the fromDate and toDate. 
64606      * If false, the time axis will adapt to the new values by adding/removing steps.
64607      * Default's [Ext.Date.DAY, 1].
64608      * 
64609      * @property constrain 
64610      * @type Boolean
64611      */
64612     constrain: false,
64613     
64614     // @private a wrapper for date methods.
64615     dateMethods: {
64616         'year': function(date) {
64617             return date.getFullYear();
64618         },
64619         'month': function(date) {
64620             return date.getMonth() + 1;
64621         },
64622         'day': function(date) {
64623             return date.getDate();
64624         },
64625         'hour': function(date) {
64626             return date.getHours();
64627         },
64628         'minute': function(date) {
64629             return date.getMinutes();
64630         },
64631         'second': function(date) {
64632             return date.getSeconds();
64633         },
64634         'millisecond': function(date) {
64635             return date.getMilliseconds();
64636         }
64637     },
64638     
64639     // @private holds aggregate functions.
64640     aggregateFn: (function() {
64641         var etype = (function() {
64642             var rgxp = /^\[object\s(.*)\]$/,
64643                 toString = Object.prototype.toString;
64644             return function(e) {
64645                 return toString.call(e).match(rgxp)[1];
64646             };
64647         })();
64648         return {
64649             'sum': function(list) {
64650                 var i = 0, l = list.length, acum = 0;
64651                 if (!list.length || etype(list[0]) != 'Number') {
64652                     return list[0];
64653                 }
64654                 for (; i < l; i++) {
64655                     acum += list[i];
64656                 }
64657                 return acum;
64658             },
64659             'max': function(list) {
64660                 if (!list.length || etype(list[0]) != 'Number') {
64661                     return list[0];
64662                 }
64663                 return Math.max.apply(Math, list);
64664             },
64665             'min': function(list) {
64666                 if (!list.length || etype(list[0]) != 'Number') {
64667                     return list[0];
64668                 }
64669                 return Math.min.apply(Math, list);
64670             },
64671             'avg': function(list) {
64672                 var i = 0, l = list.length, acum = 0;
64673                 if (!list.length || etype(list[0]) != 'Number') {
64674                     return list[0];
64675                 }
64676                 for (; i < l; i++) {
64677                     acum += list[i];
64678                 }
64679                 return acum / l;
64680             }
64681         };
64682     })(),
64683     
64684     // @private normalized the store to fill date gaps in the time interval.
64685     constrainDates: function() {
64686         var fromDate = Ext.Date.clone(this.fromDate),
64687             toDate = Ext.Date.clone(this.toDate),
64688             step = this.step,
64689             field = this.fields,
64690             store = this.chart.store,
64691             record, recObj, fieldNames = [],
64692             newStore = Ext.create('Ext.data.Store', {
64693                 model: store.model
64694             });
64695         
64696         var getRecordByDate = (function() {
64697             var index = 0, l = store.getCount();
64698             return function(date) {
64699                 var rec, recDate;
64700                 for (; index < l; index++) {
64701                     rec = store.getAt(index);
64702                     recDate = rec.get(field);
64703                     if (+recDate > +date) {
64704                         return false;
64705                     } else if (+recDate == +date) {
64706                         return rec;
64707                     }
64708                 }
64709                 return false;
64710             };
64711         })();
64712         
64713         if (!this.constrain) {
64714             this.chart.filteredStore = this.chart.store;
64715             return;
64716         }
64717
64718         while(+fromDate <= +toDate) {
64719             record = getRecordByDate(fromDate);
64720             recObj = {};
64721             if (record) {
64722                 newStore.add(record.data);
64723             } else {
64724                 newStore.model.prototype.fields.each(function(f) {
64725                     recObj[f.name] = false;
64726                 });
64727                 recObj.date = fromDate;
64728                 newStore.add(recObj);
64729             }
64730             fromDate = Ext.Date.add(fromDate, step[0], step[1]);
64731         }
64732          
64733         this.chart.filteredStore = newStore;
64734     },
64735     
64736     // @private aggregates values if multiple store elements belong to the same time step.
64737     aggregate: function() {
64738         var aggStore = {}, 
64739             aggKeys = [], key, value,
64740             op = this.aggregateOp,
64741             field = this.fields, i,
64742             fields = this.groupBy.split(','),
64743             curField,
64744             recFields = [],
64745             recFieldsLen = 0,
64746             obj,
64747             dates = [],
64748             json = [],
64749             l = fields.length,
64750             dateMethods = this.dateMethods,
64751             aggregateFn = this.aggregateFn,
64752             store = this.chart.filteredStore || this.chart.store;
64753         
64754         store.each(function(rec) {
64755             //get all record field names in a simple array
64756             if (!recFields.length) {
64757                 rec.fields.each(function(f) {
64758                     recFields.push(f.name);
64759                 });
64760                 recFieldsLen = recFields.length;
64761             }
64762             //get record date value
64763             value = rec.get(field);
64764             //generate key for grouping records
64765             for (i = 0; i < l; i++) {
64766                 if (i == 0) {
64767                     key = String(dateMethods[fields[i]](value));
64768                 } else {
64769                     key += '||' + dateMethods[fields[i]](value);
64770                 }
64771             }
64772             //get aggregation record from hash
64773             if (key in aggStore) {
64774                 obj = aggStore[key];
64775             } else {
64776                 obj = aggStore[key] = {};
64777                 aggKeys.push(key);
64778                 dates.push(value);
64779             }
64780             //append record values to an aggregation record
64781             for (i = 0; i < recFieldsLen; i++) {
64782                 curField = recFields[i];
64783                 if (!obj[curField]) {
64784                     obj[curField] = [];
64785                 }
64786                 if (rec.get(curField) !== undefined) {
64787                     obj[curField].push(rec.get(curField));
64788                 }
64789             }
64790         });
64791         //perform aggregation operations on fields
64792         for (key in aggStore) {
64793             obj = aggStore[key];
64794             for (i = 0; i < recFieldsLen; i++) {
64795                 curField = recFields[i];
64796                 obj[curField] = aggregateFn[op](obj[curField]);
64797             }
64798             json.push(obj);
64799         }
64800         this.chart.substore = Ext.create('Ext.data.JsonStore', {
64801             fields: recFields,
64802             data: json
64803         });
64804         
64805         this.dates = dates;
64806     },
64807     
64808     // @private creates a label array to be used as the axis labels.
64809      setLabels: function() {
64810         var store = this.chart.substore,
64811             fields = this.fields,
64812             format = this.dateFormat,
64813             labels, i, dates = this.dates,
64814             formatFn = Ext.Date.format;
64815         this.labels = labels = [];
64816         store.each(function(record, i) {
64817             if (!format) {
64818                 labels.push(record.get(fields));
64819             } else {
64820                 labels.push(formatFn(dates[i], format));
64821             }
64822          }, this);
64823      },
64824
64825     processView: function() {
64826          //TODO(nico): fix this eventually...
64827          if (this.constrain) {
64828              this.constrainDates();
64829              this.aggregate();
64830              this.chart.substore = this.chart.filteredStore;
64831          } else {
64832              this.aggregate();
64833          }
64834     },
64835
64836      // @private modifies the store and creates the labels for the axes.
64837      applyData: function() {
64838         this.setLabels();
64839         var count = this.chart.substore.getCount();
64840          return {
64841              from: 0,
64842              to: count,
64843              steps: count - 1,
64844              step: 1
64845          };
64846      }
64847  });
64848
64849
64850 /**
64851  * @class Ext.chart.series.Series
64852  * 
64853  * Series is the abstract class containing the common logic to all chart series. Series includes 
64854  * methods from Labels, Highlights, Tips and Callouts mixins. This class implements the logic of handling 
64855  * mouse events, animating, hiding, showing all elements and returning the color of the series to be used as a legend item.
64856  *
64857  * ## Listeners
64858  *
64859  * The series class supports listeners via the Observable syntax. Some of these listeners are:
64860  *
64861  *  - `itemmouseup` When the user interacts with a marker.
64862  *  - `itemmousedown` When the user interacts with a marker.
64863  *  - `itemmousemove` When the user iteracts with a marker.
64864  *  - `afterrender` Will be triggered when the animation ends or when the series has been rendered completely.
64865  *
64866  * For example:
64867  *
64868  *     series: [{
64869  *             type: 'column',
64870  *             axis: 'left',
64871  *             listeners: {
64872  *                     'afterrender': function() {
64873  *                             console('afterrender');
64874  *                     }
64875  *             },
64876  *             xField: 'category',
64877  *             yField: 'data1'
64878  *     }]
64879  *     
64880  */
64881 Ext.define('Ext.chart.series.Series', {
64882
64883     /* Begin Definitions */
64884
64885     mixins: {
64886         observable: 'Ext.util.Observable',
64887         labels: 'Ext.chart.Label',
64888         highlights: 'Ext.chart.Highlight',
64889         tips: 'Ext.chart.Tip',
64890         callouts: 'Ext.chart.Callout'
64891     },
64892
64893     /* End Definitions */
64894
64895     /**
64896      * @cfg {Boolean|Object} highlight
64897      * If set to `true` it will highlight the markers or the series when hovering
64898      * with the mouse. This parameter can also be an object with the same style
64899      * properties you would apply to a {@link Ext.draw.Sprite} to apply custom
64900      * styles to markers and series.
64901      */
64902
64903     /**
64904      * @cfg {Object} tips
64905      * Add tooltips to the visualization's markers. The options for the tips are the
64906      * same configuration used with {@link Ext.tip.ToolTip}. For example:
64907      *
64908      *     tips: {
64909      *       trackMouse: true,
64910      *       width: 140,
64911      *       height: 28,
64912      *       renderer: function(storeItem, item) {
64913      *         this.setTitle(storeItem.get('name') + ': ' + storeItem.get('data1') + ' views');
64914      *       }
64915      *     },
64916      */
64917
64918     /**
64919      * @cfg {String} type
64920      * The type of series. Set in subclasses.
64921      */
64922     type: null,
64923
64924     /**
64925      * @cfg {String} title
64926      * The human-readable name of the series.
64927      */
64928     title: null,
64929
64930     /**
64931      * @cfg {Boolean} showInLegend
64932      * Whether to show this series in the legend.
64933      */
64934     showInLegend: true,
64935
64936     /**
64937      * @cfg {Function} renderer
64938      * A function that can be overridden to set custom styling properties to each rendered element.
64939      * Passes in (sprite, record, attributes, index, store) to the function.
64940      */
64941     renderer: function(sprite, record, attributes, index, store) {
64942         return attributes;
64943     },
64944
64945     /**
64946      * @cfg {Array} shadowAttributes
64947      * An array with shadow attributes
64948      */
64949     shadowAttributes: null,
64950     
64951     //@private triggerdrawlistener flag
64952     triggerAfterDraw: false,
64953
64954     /**
64955      * @cfg {Object} listeners  
64956      * An (optional) object with event callbacks. All event callbacks get the target *item* as first parameter. The callback functions are:
64957      *  
64958      *  <ul>
64959      *      <li>itemmouseover</li>
64960      *      <li>itemmouseout</li>
64961      *      <li>itemmousedown</li>
64962      *      <li>itemmouseup</li>
64963      *  </ul>
64964      */
64965     
64966     constructor: function(config) {
64967         var me = this;
64968         if (config) {
64969             Ext.apply(me, config);
64970         }
64971         
64972         me.shadowGroups = [];
64973         
64974         me.mixins.labels.constructor.call(me, config);
64975         me.mixins.highlights.constructor.call(me, config);
64976         me.mixins.tips.constructor.call(me, config);
64977         me.mixins.callouts.constructor.call(me, config);
64978
64979         me.addEvents({
64980             scope: me,
64981             itemmouseover: true,
64982             itemmouseout: true,
64983             itemmousedown: true,
64984             itemmouseup: true,
64985             mouseleave: true,
64986             afterdraw: true,
64987
64988             /**
64989              * @event titlechange
64990              * Fires when the series title is changed via {@link #setTitle}.
64991              * @param {String} title The new title value
64992              * @param {Number} index The index in the collection of titles
64993              */
64994             titlechange: true
64995         });
64996
64997         me.mixins.observable.constructor.call(me, config);
64998
64999         me.on({
65000             scope: me,
65001             itemmouseover: me.onItemMouseOver,
65002             itemmouseout: me.onItemMouseOut,
65003             mouseleave: me.onMouseLeave
65004         });
65005     },
65006
65007     // @private set the bbox and clipBox for the series
65008     setBBox: function(noGutter) {
65009         var me = this,
65010             chart = me.chart,
65011             chartBBox = chart.chartBBox,
65012             gutterX = noGutter ? 0 : chart.maxGutter[0],
65013             gutterY = noGutter ? 0 : chart.maxGutter[1],
65014             clipBox, bbox;
65015
65016         clipBox = {
65017             x: chartBBox.x,
65018             y: chartBBox.y,
65019             width: chartBBox.width,
65020             height: chartBBox.height
65021         };
65022         me.clipBox = clipBox;
65023
65024         bbox = {
65025             x: (clipBox.x + gutterX) - (chart.zoom.x * chart.zoom.width),
65026             y: (clipBox.y + gutterY) - (chart.zoom.y * chart.zoom.height),
65027             width: (clipBox.width - (gutterX * 2)) * chart.zoom.width,
65028             height: (clipBox.height - (gutterY * 2)) * chart.zoom.height
65029         };
65030         me.bbox = bbox;
65031     },
65032
65033     // @private set the animation for the sprite
65034     onAnimate: function(sprite, attr) {
65035         var me = this;
65036         sprite.stopAnimation();
65037         if (me.triggerAfterDraw) {
65038             return sprite.animate(Ext.applyIf(attr, me.chart.animate));
65039         } else {
65040             me.triggerAfterDraw = true;
65041             return sprite.animate(Ext.apply(Ext.applyIf(attr, me.chart.animate), {
65042                 listeners: {
65043                     'afteranimate': function() {
65044                         me.triggerAfterDraw = false;
65045                         me.fireEvent('afterrender');
65046                     }    
65047                 }    
65048             }));
65049         }
65050     },
65051     
65052     // @private return the gutter.
65053     getGutters: function() {
65054         return [0, 0];
65055     },
65056
65057     // @private wrapper for the itemmouseover event.
65058     onItemMouseOver: function(item) { 
65059         var me = this;
65060         if (item.series === me) {
65061             if (me.highlight) {
65062                 me.highlightItem(item);
65063             }
65064             if (me.tooltip) {
65065                 me.showTip(item);
65066             }
65067         }
65068     },
65069
65070     // @private wrapper for the itemmouseout event.
65071     onItemMouseOut: function(item) {
65072         var me = this;
65073         if (item.series === me) {
65074             me.unHighlightItem();
65075             if (me.tooltip) {
65076                 me.hideTip(item);
65077             }
65078         }
65079     },
65080
65081     // @private wrapper for the mouseleave event.
65082     onMouseLeave: function() {
65083         var me = this;
65084         me.unHighlightItem();
65085         if (me.tooltip) {
65086             me.hideTip();
65087         }
65088     },
65089
65090     /**
65091      * For a given x/y point relative to the Surface, find a corresponding item from this
65092      * series, if any.
65093      * @param {Number} x
65094      * @param {Number} y
65095      * @return {Object} An object describing the item, or null if there is no matching item. The exact contents of
65096      *                  this object will vary by series type, but should always contain at least the following:
65097      *                  <ul>
65098      *                    <li>{Ext.chart.series.Series} series - the Series object to which the item belongs</li>
65099      *                    <li>{Object} value - the value(s) of the item's data point</li>
65100      *                    <li>{Array} point - the x/y coordinates relative to the chart box of a single point
65101      *                        for this data item, which can be used as e.g. a tooltip anchor point.</li>
65102      *                    <li>{Ext.draw.Sprite} sprite - the item's rendering Sprite.
65103      *                  </ul>
65104      */
65105     getItemForPoint: function(x, y) {
65106         //if there are no items to query just return null.
65107         if (!this.items || !this.items.length || this.seriesIsHidden) {
65108             return null;
65109         }
65110         var me = this,
65111             items = me.items,
65112             bbox = me.bbox,
65113             item, i, ln;
65114         // Check bounds
65115         if (!Ext.draw.Draw.withinBox(x, y, bbox)) {
65116             return null;
65117         }
65118         for (i = 0, ln = items.length; i < ln; i++) {
65119             if (items[i] && this.isItemInPoint(x, y, items[i], i)) {
65120                 return items[i];
65121             }
65122         }
65123         
65124         return null;
65125     },
65126     
65127     isItemInPoint: function(x, y, item, i) {
65128         return false;
65129     },
65130
65131     /**
65132      * Hides all the elements in the series.
65133      */
65134     hideAll: function() {
65135         var me = this,
65136             items = me.items,
65137             item, len, i, sprite;
65138
65139         me.seriesIsHidden = true;
65140         me._prevShowMarkers = me.showMarkers;
65141
65142         me.showMarkers = false;
65143         //hide all labels
65144         me.hideLabels(0);
65145         //hide all sprites
65146         for (i = 0, len = items.length; i < len; i++) {
65147             item = items[i];
65148             sprite = item.sprite;
65149             if (sprite) {
65150                 sprite.setAttributes({
65151                     hidden: true
65152                 }, true);
65153             }
65154         }
65155     },
65156
65157     /**
65158      * Shows all the elements in the series.
65159      */
65160     showAll: function() {
65161         var me = this,
65162             prevAnimate = me.chart.animate;
65163         me.chart.animate = false;
65164         me.seriesIsHidden = false;
65165         me.showMarkers = me._prevShowMarkers;
65166         me.drawSeries();
65167         me.chart.animate = prevAnimate;
65168     },
65169     
65170     /**
65171      * Returns a string with the color to be used for the series legend item. 
65172      */
65173     getLegendColor: function(index) {
65174         var me = this, fill, stroke;
65175         if (me.seriesStyle) {
65176             fill = me.seriesStyle.fill;
65177             stroke = me.seriesStyle.stroke;
65178             if (fill && fill != 'none') {
65179                 return fill;
65180             }
65181             return stroke;
65182         }
65183         return '#000';
65184     },
65185     
65186     /**
65187      * Checks whether the data field should be visible in the legend
65188      * @private
65189      * @param {Number} index The index of the current item
65190      */
65191     visibleInLegend: function(index){
65192         var excludes = this.__excludes;
65193         if (excludes) {
65194             return !excludes[index];
65195         }
65196         return !this.seriesIsHidden;
65197     },
65198
65199     /**
65200      * Changes the value of the {@link #title} for the series.
65201      * Arguments can take two forms:
65202      * <ul>
65203      * <li>A single String value: this will be used as the new single title for the series (applies
65204      * to series with only one yField)</li>
65205      * <li>A numeric index and a String value: this will set the title for a single indexed yField.</li>
65206      * </ul>
65207      * @param {Number} index
65208      * @param {String} title
65209      */
65210     setTitle: function(index, title) {
65211         var me = this,
65212             oldTitle = me.title;
65213
65214         if (Ext.isString(index)) {
65215             title = index;
65216             index = 0;
65217         }
65218
65219         if (Ext.isArray(oldTitle)) {
65220             oldTitle[index] = title;
65221         } else {
65222             me.title = title;
65223         }
65224
65225         me.fireEvent('titlechange', title, index);
65226     }
65227 });
65228
65229 /**
65230  * @class Ext.chart.series.Cartesian
65231  * @extends Ext.chart.series.Series
65232  *
65233  * Common base class for series implementations which plot values using x/y coordinates.
65234  *
65235  * @constructor
65236  */
65237 Ext.define('Ext.chart.series.Cartesian', {
65238
65239     /* Begin Definitions */
65240
65241     extend: 'Ext.chart.series.Series',
65242
65243     alternateClassName: ['Ext.chart.CartesianSeries', 'Ext.chart.CartesianChart'],
65244
65245     /* End Definitions */
65246
65247     /**
65248      * The field used to access the x axis value from the items from the data
65249      * source.
65250      *
65251      * @cfg xField
65252      * @type String
65253      */
65254     xField: null,
65255
65256     /**
65257      * The field used to access the y-axis value from the items from the data
65258      * source.
65259      *
65260      * @cfg yField
65261      * @type String
65262      */
65263     yField: null,
65264
65265     /**
65266      * Indicates which axis the series will bind to
65267      *
65268      * @property axis
65269      * @type String
65270      */
65271     axis: 'left'
65272 });
65273
65274 /**
65275  * @class Ext.chart.series.Area
65276  * @extends Ext.chart.series.Cartesian
65277  * 
65278  <p>
65279     Creates a Stacked Area Chart. The stacked area chart is useful when displaying multiple aggregated layers of information.
65280     As with all other series, the Area Series must be appended in the *series* Chart array configuration. See the Chart 
65281     documentation for more information. A typical configuration object for the area series could be:
65282  </p>
65283 {@img Ext.chart.series.Area/Ext.chart.series.Area.png Ext.chart.series.Area chart series} 
65284   <pre><code>
65285    var store = Ext.create('Ext.data.JsonStore', {
65286         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
65287         data: [
65288             {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
65289             {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
65290             {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
65291             {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
65292             {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}                                                
65293         ]
65294     });
65295     
65296     Ext.create('Ext.chart.Chart', {
65297         renderTo: Ext.getBody(),
65298         width: 500,
65299         height: 300,
65300         store: store,
65301         axes: [{
65302             type: 'Numeric',
65303             grid: true,
65304             position: 'left',
65305             fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
65306             title: 'Sample Values',
65307             grid: {
65308                 odd: {
65309                     opacity: 1,
65310                     fill: '#ddd',
65311                     stroke: '#bbb',
65312                     'stroke-width': 1
65313                 }
65314             },
65315             minimum: 0,
65316             adjustMinimumByMajorUnit: 0
65317         }, {
65318             type: 'Category',
65319             position: 'bottom',
65320             fields: ['name'],
65321             title: 'Sample Metrics',
65322             grid: true,
65323             label: {
65324                 rotate: {
65325                     degrees: 315
65326                 }
65327             }
65328         }],
65329         series: [{
65330             type: 'area',
65331             highlight: false,
65332             axis: 'left',
65333             xField: 'name',
65334             yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
65335             style: {
65336                 opacity: 0.93
65337             }
65338         }]
65339     });
65340    </code></pre>
65341  
65342   
65343  <p>
65344   In this configuration we set `area` as the type for the series, set highlighting options to true for highlighting elements on hover, 
65345   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, 
65346   and as yFields (aggregated layers) seven data fields from the same store. Then we override some theming styles by adding some opacity 
65347   to the style object.
65348  </p>
65349   
65350  * @xtype area
65351  * 
65352  */
65353 Ext.define('Ext.chart.series.Area', {
65354
65355     /* Begin Definitions */
65356
65357     extend: 'Ext.chart.series.Cartesian',
65358     
65359     alias: 'series.area',
65360
65361     requires: ['Ext.chart.axis.Axis', 'Ext.draw.Color', 'Ext.fx.Anim'],
65362
65363     /* End Definitions */
65364
65365     type: 'area',
65366
65367     // @private Area charts are alyways stacked
65368     stacked: true,
65369
65370     /**
65371      * @cfg {Object} style 
65372      * Append styling properties to this object for it to override theme properties.
65373      */
65374     style: {},
65375
65376     constructor: function(config) {
65377         this.callParent(arguments);
65378         var me = this,
65379             surface = me.chart.surface,
65380             i, l;
65381         Ext.apply(me, config, {
65382             __excludes: [],
65383             highlightCfg: {
65384                 lineWidth: 3,
65385                 stroke: '#55c',
65386                 opacity: 0.8,
65387                 color: '#f00'
65388             }
65389         });
65390         if (me.highlight) {
65391             me.highlightSprite = surface.add({
65392                 type: 'path',
65393                 path: ['M', 0, 0],
65394                 zIndex: 1000,
65395                 opacity: 0.3,
65396                 lineWidth: 5,
65397                 hidden: true,
65398                 stroke: '#444'
65399             });
65400         }
65401         me.group = surface.getGroup(me.seriesId);
65402     },
65403
65404     // @private Shrinks dataSets down to a smaller size
65405     shrink: function(xValues, yValues, size) {
65406         var len = xValues.length,
65407             ratio = Math.floor(len / size),
65408             i, j,
65409             xSum = 0,
65410             yCompLen = this.areas.length,
65411             ySum = [],
65412             xRes = [],
65413             yRes = [];
65414         //initialize array
65415         for (j = 0; j < yCompLen; ++j) {
65416             ySum[j] = 0;
65417         }
65418         for (i = 0; i < len; ++i) {
65419             xSum += xValues[i];
65420             for (j = 0; j < yCompLen; ++j) {
65421                 ySum[j] += yValues[i][j];
65422             }
65423             if (i % ratio == 0) {
65424                 //push averages
65425                 xRes.push(xSum/ratio);
65426                 for (j = 0; j < yCompLen; ++j) {
65427                     ySum[j] /= ratio;
65428                 }
65429                 yRes.push(ySum);
65430                 //reset sum accumulators
65431                 xSum = 0;
65432                 for (j = 0, ySum = []; j < yCompLen; ++j) {
65433                     ySum[j] = 0;
65434                 }
65435             }
65436         }
65437         return {
65438             x: xRes,
65439             y: yRes
65440         };
65441     },
65442
65443     // @private Get chart and data boundaries
65444     getBounds: function() {
65445         var me = this,
65446             chart = me.chart,
65447             store = chart.substore || chart.store,
65448             areas = [].concat(me.yField),
65449             areasLen = areas.length,
65450             xValues = [],
65451             yValues = [],
65452             infinity = Infinity,
65453             minX = infinity,
65454             minY = infinity,
65455             maxX = -infinity,
65456             maxY = -infinity,
65457             math = Math,
65458             mmin = math.min,
65459             mmax = math.max,
65460             bbox, xScale, yScale, xValue, yValue, areaIndex, acumY, ln, sumValues, clipBox, areaElem;
65461
65462         me.setBBox();
65463         bbox = me.bbox;
65464
65465         // Run through the axis
65466         if (me.axis) {
65467             axis = chart.axes.get(me.axis);
65468             if (axis) {
65469                 out = axis.calcEnds();
65470                 minY = out.from || axis.prevMin;
65471                 maxY = mmax(out.to || axis.prevMax, 0);
65472             }
65473         }
65474
65475         if (me.yField && !Ext.isNumber(minY)) {
65476             axis = Ext.create('Ext.chart.axis.Axis', {
65477                 chart: chart,
65478                 fields: [].concat(me.yField)
65479             });
65480             out = axis.calcEnds();
65481             minY = out.from || axis.prevMin;
65482             maxY = mmax(out.to || axis.prevMax, 0);
65483         }
65484
65485         if (!Ext.isNumber(minY)) {
65486             minY = 0;
65487         }
65488         if (!Ext.isNumber(maxY)) {
65489             maxY = 0;
65490         }
65491
65492         store.each(function(record, i) {
65493             xValue = record.get(me.xField);
65494             yValue = [];
65495             if (typeof xValue != 'number') {
65496                 xValue = i;
65497             }
65498             xValues.push(xValue);
65499             acumY = 0;
65500             for (areaIndex = 0; areaIndex < areasLen; areaIndex++) {
65501                 areaElem = record.get(areas[areaIndex]);
65502                 if (typeof areaElem == 'number') {
65503                     minY = mmin(minY, areaElem);
65504                     yValue.push(areaElem);
65505                     acumY += areaElem;
65506                 }
65507             }
65508             minX = mmin(minX, xValue);
65509             maxX = mmax(maxX, xValue);
65510             maxY = mmax(maxY, acumY);
65511             yValues.push(yValue);
65512         }, me);
65513
65514         xScale = bbox.width / (maxX - minX);
65515         yScale = bbox.height / (maxY - minY);
65516
65517         ln = xValues.length;
65518         if ((ln > bbox.width) && me.areas) {
65519             sumValues = me.shrink(xValues, yValues, bbox.width);
65520             xValues = sumValues.x;
65521             yValues = sumValues.y;
65522         }
65523
65524         return {
65525             bbox: bbox,
65526             minX: minX,
65527             minY: minY,
65528             xValues: xValues,
65529             yValues: yValues,
65530             xScale: xScale,
65531             yScale: yScale,
65532             areasLen: areasLen
65533         };
65534     },
65535
65536     // @private Build an array of paths for the chart
65537     getPaths: function() {
65538         var me = this,
65539             chart = me.chart,
65540             store = chart.substore || chart.store,
65541             first = true,
65542             bounds = me.getBounds(),
65543             bbox = bounds.bbox,
65544             items = me.items = [],
65545             componentPaths = [],
65546             componentPath,
65547             paths = [],
65548             i, ln, x, y, xValue, yValue, acumY, areaIndex, prevAreaIndex, areaElem, path;
65549
65550         ln = bounds.xValues.length;
65551         // Start the path
65552         for (i = 0; i < ln; i++) {
65553             xValue = bounds.xValues[i];
65554             yValue = bounds.yValues[i];
65555             x = bbox.x + (xValue - bounds.minX) * bounds.xScale;
65556             acumY = 0;
65557             for (areaIndex = 0; areaIndex < bounds.areasLen; areaIndex++) {
65558                 // Excluded series
65559                 if (me.__excludes[areaIndex]) {
65560                     continue;
65561                 }
65562                 if (!componentPaths[areaIndex]) {
65563                     componentPaths[areaIndex] = [];
65564                 }
65565                 areaElem = yValue[areaIndex];
65566                 acumY += areaElem;
65567                 y = bbox.y + bbox.height - (acumY - bounds.minY) * bounds.yScale;
65568                 if (!paths[areaIndex]) {
65569                     paths[areaIndex] = ['M', x, y];
65570                     componentPaths[areaIndex].push(['L', x, y]);
65571                 } else {
65572                     paths[areaIndex].push('L', x, y);
65573                     componentPaths[areaIndex].push(['L', x, y]);
65574                 }
65575                 if (!items[areaIndex]) {
65576                     items[areaIndex] = {
65577                         pointsUp: [],
65578                         pointsDown: [],
65579                         series: me
65580                     };
65581                 }
65582                 items[areaIndex].pointsUp.push([x, y]);
65583             }
65584         }
65585         
65586         // Close the paths
65587         for (areaIndex = 0; areaIndex < bounds.areasLen; areaIndex++) {
65588             // Excluded series
65589             if (me.__excludes[areaIndex]) {
65590                 continue;
65591             }
65592             path = paths[areaIndex];
65593             // Close bottom path to the axis
65594             if (areaIndex == 0 || first) {
65595                 first = false;
65596                 path.push('L', x, bbox.y + bbox.height,
65597                           'L', bbox.x, bbox.y + bbox.height,
65598                           'Z');
65599             }
65600             // Close other paths to the one before them
65601             else {
65602                 componentPath = componentPaths[prevAreaIndex];
65603                 componentPath.reverse();
65604                 path.push('L', x, componentPath[0][2]);
65605                 for (i = 0; i < ln; i++) {
65606                     path.push(componentPath[i][0],
65607                               componentPath[i][1],
65608                               componentPath[i][2]);
65609                     items[areaIndex].pointsDown[ln -i -1] = [componentPath[i][1], componentPath[i][2]];
65610                 }
65611                 path.push('L', bbox.x, path[2], 'Z');
65612             }
65613             prevAreaIndex = areaIndex;
65614         }
65615         return {
65616             paths: paths,
65617             areasLen: bounds.areasLen
65618         };
65619     },
65620
65621     /**
65622      * Draws the series for the current chart.
65623      */
65624     drawSeries: function() {
65625         var me = this,
65626             chart = me.chart,
65627             store = chart.substore || chart.store,
65628             surface = chart.surface,
65629             animate = chart.animate,
65630             group = me.group,
65631             endLineStyle = Ext.apply(me.seriesStyle, me.style),
65632             colorArrayStyle = me.colorArrayStyle,
65633             colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
65634             areaIndex, areaElem, paths, path, rendererAttributes;
65635
65636         me.unHighlightItem();
65637         me.cleanHighlights();
65638
65639         if (!store || !store.getCount()) {
65640             return;
65641         }
65642         
65643         paths = me.getPaths();
65644
65645         if (!me.areas) {
65646             me.areas = [];
65647         }
65648
65649         for (areaIndex = 0; areaIndex < paths.areasLen; areaIndex++) {
65650             // Excluded series
65651             if (me.__excludes[areaIndex]) {
65652                 continue;
65653             }
65654             if (!me.areas[areaIndex]) {
65655                 me.items[areaIndex].sprite = me.areas[areaIndex] = surface.add(Ext.apply({}, {
65656                     type: 'path',
65657                     group: group,
65658                     // 'clip-rect': me.clipBox,
65659                     path: paths.paths[areaIndex],
65660                     stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength],
65661                     fill: colorArrayStyle[areaIndex % colorArrayLength]
65662                 }, endLineStyle || {}));
65663             }
65664             areaElem = me.areas[areaIndex];
65665             path = paths.paths[areaIndex];
65666             if (animate) {
65667                 //Add renderer to line. There is not a unique record associated with this.
65668                 rendererAttributes = me.renderer(areaElem, false, { 
65669                     path: path,
65670                     // 'clip-rect': me.clipBox,
65671                     fill: colorArrayStyle[areaIndex % colorArrayLength],
65672                     stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength]
65673                 }, areaIndex, store);
65674                 //fill should not be used here but when drawing the special fill path object
65675                 me.animation = me.onAnimate(areaElem, {
65676                     to: rendererAttributes
65677                 });
65678             } else {
65679                 rendererAttributes = me.renderer(areaElem, false, { 
65680                     path: path,
65681                     // 'clip-rect': me.clipBox,
65682                     hidden: false,
65683                     fill: colorArrayStyle[areaIndex % colorArrayLength],
65684                     stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength]
65685                 }, areaIndex, store);
65686                 me.areas[areaIndex].setAttributes(rendererAttributes, true);
65687             }
65688         }
65689         me.renderLabels();
65690         me.renderCallouts();
65691     },
65692
65693     // @private
65694     onAnimate: function(sprite, attr) {
65695         sprite.show();
65696         return this.callParent(arguments);
65697     },
65698
65699     // @private
65700     onCreateLabel: function(storeItem, item, i, display) {
65701         var me = this,
65702             group = me.labelsGroup,
65703             config = me.label,
65704             bbox = me.bbox,
65705             endLabelStyle = Ext.apply(config, me.seriesLabelStyle);
65706
65707         return me.chart.surface.add(Ext.apply({
65708             'type': 'text',
65709             'text-anchor': 'middle',
65710             'group': group,
65711             'x': item.point[0],
65712             'y': bbox.y + bbox.height / 2
65713         }, endLabelStyle || {}));
65714     },
65715
65716     // @private
65717     onPlaceLabel: function(label, storeItem, item, i, display, animate, index) {
65718         var me = this,
65719             chart = me.chart,
65720             resizing = chart.resizing,
65721             config = me.label,
65722             format = config.renderer,
65723             field = config.field,
65724             bbox = me.bbox,
65725             x = item.point[0],
65726             y = item.point[1],
65727             bb, width, height;
65728         
65729         label.setAttributes({
65730             text: format(storeItem.get(field[index])),
65731             hidden: true
65732         }, true);
65733         
65734         bb = label.getBBox();
65735         width = bb.width / 2;
65736         height = bb.height / 2;
65737         
65738         x = x - width < bbox.x? bbox.x + width : x;
65739         x = (x + width > bbox.x + bbox.width) ? (x - (x + width - bbox.x - bbox.width)) : x;
65740         y = y - height < bbox.y? bbox.y + height : y;
65741         y = (y + height > bbox.y + bbox.height) ? (y - (y + height - bbox.y - bbox.height)) : y;
65742
65743         if (me.chart.animate && !me.chart.resizing) {
65744             label.show(true);
65745             me.onAnimate(label, {
65746                 to: {
65747                     x: x,
65748                     y: y
65749                 }
65750             });
65751         } else {
65752             label.setAttributes({
65753                 x: x,
65754                 y: y
65755             }, true);
65756             if (resizing) {
65757                 me.animation.on('afteranimate', function() {
65758                     label.show(true);
65759                 });
65760             } else {
65761                 label.show(true);
65762             }
65763         }
65764     },
65765
65766     // @private
65767     onPlaceCallout : function(callout, storeItem, item, i, display, animate, index) {
65768         var me = this,
65769             chart = me.chart,
65770             surface = chart.surface,
65771             resizing = chart.resizing,
65772             config = me.callouts,
65773             items = me.items,
65774             prev = (i == 0) ? false : items[i -1].point,
65775             next = (i == items.length -1) ? false : items[i +1].point,
65776             cur = item.point,
65777             dir, norm, normal, a, aprev, anext,
65778             bbox = callout.label.getBBox(),
65779             offsetFromViz = 30,
65780             offsetToSide = 10,
65781             offsetBox = 3,
65782             boxx, boxy, boxw, boxh,
65783             p, clipRect = me.clipRect,
65784             x, y;
65785
65786         //get the right two points
65787         if (!prev) {
65788             prev = cur;
65789         }
65790         if (!next) {
65791             next = cur;
65792         }
65793         a = (next[1] - prev[1]) / (next[0] - prev[0]);
65794         aprev = (cur[1] - prev[1]) / (cur[0] - prev[0]);
65795         anext = (next[1] - cur[1]) / (next[0] - cur[0]);
65796         
65797         norm = Math.sqrt(1 + a * a);
65798         dir = [1 / norm, a / norm];
65799         normal = [-dir[1], dir[0]];
65800         
65801         //keep the label always on the outer part of the "elbow"
65802         if (aprev > 0 && anext < 0 && normal[1] < 0 || aprev < 0 && anext > 0 && normal[1] > 0) {
65803             normal[0] *= -1;
65804             normal[1] *= -1;
65805         } else if (Math.abs(aprev) < Math.abs(anext) && normal[0] < 0 || Math.abs(aprev) > Math.abs(anext) && normal[0] > 0) {
65806             normal[0] *= -1;
65807             normal[1] *= -1;
65808         }
65809
65810         //position
65811         x = cur[0] + normal[0] * offsetFromViz;
65812         y = cur[1] + normal[1] * offsetFromViz;
65813         
65814         //box position and dimensions
65815         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
65816         boxy = y - bbox.height /2 - offsetBox;
65817         boxw = bbox.width + 2 * offsetBox;
65818         boxh = bbox.height + 2 * offsetBox;
65819         
65820         //now check if we're out of bounds and invert the normal vector correspondingly
65821         //this may add new overlaps between labels (but labels won't be out of bounds).
65822         if (boxx < clipRect[0] || (boxx + boxw) > (clipRect[0] + clipRect[2])) {
65823             normal[0] *= -1;
65824         }
65825         if (boxy < clipRect[1] || (boxy + boxh) > (clipRect[1] + clipRect[3])) {
65826             normal[1] *= -1;
65827         }
65828
65829         //update positions
65830         x = cur[0] + normal[0] * offsetFromViz;
65831         y = cur[1] + normal[1] * offsetFromViz;
65832         
65833         //update box position and dimensions
65834         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
65835         boxy = y - bbox.height /2 - offsetBox;
65836         boxw = bbox.width + 2 * offsetBox;
65837         boxh = bbox.height + 2 * offsetBox;
65838         
65839         //set the line from the middle of the pie to the box.
65840         callout.lines.setAttributes({
65841             path: ["M", cur[0], cur[1], "L", x, y, "Z"]
65842         }, true);
65843         //set box position
65844         callout.box.setAttributes({
65845             x: boxx,
65846             y: boxy,
65847             width: boxw,
65848             height: boxh
65849         }, true);
65850         //set text position
65851         callout.label.setAttributes({
65852             x: x + (normal[0] > 0? offsetBox : -(bbox.width + offsetBox)),
65853             y: y
65854         }, true);
65855         for (p in callout) {
65856             callout[p].show(true);
65857         }
65858     },
65859     
65860     isItemInPoint: function(x, y, item, i) {
65861         var me = this,
65862             pointsUp = item.pointsUp,
65863             pointsDown = item.pointsDown,
65864             abs = Math.abs,
65865             dist = Infinity, p, pln, point;
65866         
65867         for (p = 0, pln = pointsUp.length; p < pln; p++) {
65868             point = [pointsUp[p][0], pointsUp[p][1]];
65869             if (dist > abs(x - point[0])) {
65870                 dist = abs(x - point[0]);
65871             } else {
65872                 point = pointsUp[p -1];
65873                 if (y >= point[1] && (!pointsDown.length || y <= (pointsDown[p -1][1]))) {
65874                     item.storeIndex = p -1;
65875                     item.storeField = me.yField[i];
65876                     item.storeItem = me.chart.store.getAt(p -1);
65877                     item._points = pointsDown.length? [point, pointsDown[p -1]] : [point];
65878                     return true;
65879                 } else {
65880                     break;
65881                 }
65882             }
65883         }
65884         return false;
65885     },
65886
65887     /**
65888      * Highlight this entire series.
65889      * @param {Object} item Info about the item; same format as returned by #getItemForPoint.
65890      */
65891     highlightSeries: function() {
65892         var area, to, fillColor;
65893         if (this._index !== undefined) {
65894             area = this.areas[this._index];
65895             if (area.__highlightAnim) {
65896                 area.__highlightAnim.paused = true;
65897             }
65898             area.__highlighted = true;
65899             area.__prevOpacity = area.__prevOpacity || area.attr.opacity || 1;
65900             area.__prevFill = area.__prevFill || area.attr.fill;
65901             area.__prevLineWidth = area.__prevLineWidth || area.attr.lineWidth;
65902             fillColor = Ext.draw.Color.fromString(area.__prevFill);
65903             to = {
65904                 lineWidth: (area.__prevLineWidth || 0) + 2
65905             };
65906             if (fillColor) {
65907                 to.fill = fillColor.getLighter(0.2).toString();
65908             }
65909             else {
65910                 to.opacity = Math.max(area.__prevOpacity - 0.3, 0);
65911             }
65912             if (this.chart.animate) {
65913                 area.__highlightAnim = Ext.create('Ext.fx.Anim', Ext.apply({
65914                     target: area,
65915                     to: to
65916                 }, this.chart.animate));
65917             }
65918             else {
65919                 area.setAttributes(to, true);
65920             }
65921         }
65922     },
65923
65924     /**
65925      * UnHighlight this entire series.
65926      * @param {Object} item Info about the item; same format as returned by #getItemForPoint.
65927      */
65928     unHighlightSeries: function() {
65929         var area;
65930         if (this._index !== undefined) {
65931             area = this.areas[this._index];
65932             if (area.__highlightAnim) {
65933                 area.__highlightAnim.paused = true;
65934             }
65935             if (area.__highlighted) {
65936                 area.__highlighted = false;
65937                 area.__highlightAnim = Ext.create('Ext.fx.Anim', {
65938                     target: area,
65939                     to: {
65940                         fill: area.__prevFill,
65941                         opacity: area.__prevOpacity,
65942                         lineWidth: area.__prevLineWidth
65943                     }
65944                 });
65945             }
65946         }
65947     },
65948
65949     /**
65950      * Highlight the specified item. If no item is provided the whole series will be highlighted.
65951      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
65952      */
65953     highlightItem: function(item) {
65954         var me = this,
65955             points, path;
65956         if (!item) {
65957             this.highlightSeries();
65958             return;
65959         }
65960         points = item._points;
65961         path = points.length == 2? ['M', points[0][0], points[0][1], 'L', points[1][0], points[1][1]]
65962                 : ['M', points[0][0], points[0][1], 'L', points[0][0], me.bbox.y + me.bbox.height];
65963         me.highlightSprite.setAttributes({
65964             path: path,
65965             hidden: false
65966         }, true);
65967     },
65968
65969     /**
65970      * un-highlights the specified item. If no item is provided it will un-highlight the entire series.
65971      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
65972      */
65973     unHighlightItem: function(item) {
65974         if (!item) {
65975             this.unHighlightSeries();
65976         }
65977
65978         if (this.highlightSprite) {
65979             this.highlightSprite.hide(true);
65980         }
65981     },
65982
65983     // @private
65984     hideAll: function() {
65985         if (!isNaN(this._index)) {
65986             this.__excludes[this._index] = true;
65987             this.areas[this._index].hide(true);
65988             this.drawSeries();
65989         }
65990     },
65991
65992     // @private
65993     showAll: function() {
65994         if (!isNaN(this._index)) {
65995             this.__excludes[this._index] = false;
65996             this.areas[this._index].show(true);
65997             this.drawSeries();
65998         }
65999     },
66000
66001     /**
66002      * Returns the color of the series (to be displayed as color for the series legend item).
66003      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
66004      */
66005     getLegendColor: function(index) {
66006         var me = this;
66007         return me.colorArrayStyle[index % me.colorArrayStyle.length];
66008     }
66009 });
66010
66011 /**
66012  * Creates a Bar Chart. A Bar Chart is a useful visualization technique to display quantitative information for
66013  * different categories that can show some progression (or regression) in the dataset. As with all other series, the Bar
66014  * Series must be appended in the *series* Chart array configuration. See the Chart documentation for more information.
66015  * A typical configuration object for the bar series could be:
66016  *
66017  * {@img Ext.chart.series.Bar/Ext.chart.series.Bar.png Ext.chart.series.Bar chart series}
66018  *
66019  *     var store = Ext.create('Ext.data.JsonStore', {
66020  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
66021  *         data: [
66022  *             {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
66023  *             {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
66024  *             {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
66025  *             {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
66026  *             {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}
66027  *         ]
66028  *     });
66029  *     
66030  *     Ext.create('Ext.chart.Chart', {
66031  *         renderTo: Ext.getBody(),
66032  *         width: 500,
66033  *         height: 300,
66034  *         animate: true,
66035  *         store: store,
66036  *         axes: [{
66037  *             type: 'Numeric',
66038  *             position: 'bottom',
66039  *             fields: ['data1'],
66040  *             label: {
66041  *                 renderer: Ext.util.Format.numberRenderer('0,0')
66042  *             },
66043  *             title: 'Sample Values',
66044  *             grid: true,
66045  *             minimum: 0
66046  *         }, {
66047  *             type: 'Category',
66048  *             position: 'left',
66049  *             fields: ['name'],
66050  *             title: 'Sample Metrics'
66051  *         }],
66052  *         series: [{
66053  *             type: 'bar',
66054  *             axis: 'bottom',
66055  *             highlight: true,
66056  *             tips: {
66057  *               trackMouse: true,
66058  *               width: 140,
66059  *               height: 28,
66060  *               renderer: function(storeItem, item) {
66061  *                 this.setTitle(storeItem.get('name') + ': ' + storeItem.get('data1') + ' views');
66062  *               }
66063  *             },
66064  *             label: {
66065  *               display: 'insideEnd',
66066  *                 field: 'data1',
66067  *                 renderer: Ext.util.Format.numberRenderer('0'),
66068  *                 orientation: 'horizontal',
66069  *                 color: '#333',
66070  *                 'text-anchor': 'middle'
66071  *             },
66072  *             xField: 'name',
66073  *             yField: ['data1']
66074  *         }]
66075  *     });
66076  *
66077  * In this configuration we set `bar` as the series type, bind the values of the bar to the bottom axis and set the
66078  * xField or category field to the `name` parameter of the store. We also set `highlight` to true which enables smooth
66079  * animations when bars are hovered. We also set some configuration for the bar labels to be displayed inside the bar,
66080  * to display the information found in the `data1` property of each element store, to render a formated text with the
66081  * `Ext.util.Format` we pass in, to have an `horizontal` orientation (as opposed to a vertical one) and we also set
66082  * other styles like `color`, `text-anchor`, etc.
66083  */
66084 Ext.define('Ext.chart.series.Bar', {
66085
66086     /* Begin Definitions */
66087
66088     extend: 'Ext.chart.series.Cartesian',
66089
66090     alternateClassName: ['Ext.chart.BarSeries', 'Ext.chart.BarChart', 'Ext.chart.StackedBarChart'],
66091
66092     requires: ['Ext.chart.axis.Axis', 'Ext.fx.Anim'],
66093
66094     /* End Definitions */
66095
66096     type: 'bar',
66097
66098     alias: 'series.bar',
66099     /**
66100      * @cfg {Boolean} column Whether to set the visualization as column chart or horizontal bar chart.
66101      */
66102     column: false,
66103     
66104     /**
66105      * @cfg style Style properties that will override the theming series styles.
66106      */
66107     style: {},
66108     
66109     /**
66110      * @cfg {Number} gutter The gutter space between single bars, as a percentage of the bar width
66111      */
66112     gutter: 38.2,
66113
66114     /**
66115      * @cfg {Number} groupGutter The gutter space between groups of bars, as a percentage of the bar width
66116      */
66117     groupGutter: 38.2,
66118
66119     /**
66120      * @cfg {Number} xPadding Padding between the left/right axes and the bars
66121      */
66122     xPadding: 0,
66123
66124     /**
66125      * @cfg {Number} yPadding Padding between the top/bottom axes and the bars
66126      */
66127     yPadding: 10,
66128
66129     constructor: function(config) {
66130         this.callParent(arguments);
66131         var me = this,
66132             surface = me.chart.surface,
66133             shadow = me.chart.shadow,
66134             i, l;
66135         Ext.apply(me, config, {
66136             highlightCfg: {
66137                 lineWidth: 3,
66138                 stroke: '#55c',
66139                 opacity: 0.8,
66140                 color: '#f00'
66141             },
66142             
66143             shadowAttributes: [{
66144                 "stroke-width": 6,
66145                 "stroke-opacity": 0.05,
66146                 stroke: 'rgb(200, 200, 200)',
66147                 translate: {
66148                     x: 1.2,
66149                     y: 1.2
66150                 }
66151             }, {
66152                 "stroke-width": 4,
66153                 "stroke-opacity": 0.1,
66154                 stroke: 'rgb(150, 150, 150)',
66155                 translate: {
66156                     x: 0.9,
66157                     y: 0.9
66158                 }
66159             }, {
66160                 "stroke-width": 2,
66161                 "stroke-opacity": 0.15,
66162                 stroke: 'rgb(100, 100, 100)',
66163                 translate: {
66164                     x: 0.6,
66165                     y: 0.6
66166                 }
66167             }]
66168         });
66169         me.group = surface.getGroup(me.seriesId + '-bars');
66170         if (shadow) {
66171             for (i = 0, l = me.shadowAttributes.length; i < l; i++) {
66172                 me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
66173             }
66174         }
66175     },
66176
66177     // @private sets the bar girth.
66178     getBarGirth: function() {
66179         var me = this,
66180             store = me.chart.store,
66181             column = me.column,
66182             ln = store.getCount(),
66183             gutter = me.gutter / 100;
66184         
66185         return (me.chart.chartBBox[column ? 'width' : 'height'] - me[column ? 'xPadding' : 'yPadding'] * 2) / (ln * (gutter + 1) - gutter);
66186     },
66187
66188     // @private returns the gutters.
66189     getGutters: function() {
66190         var me = this,
66191             column = me.column,
66192             gutter = Math.ceil(me[column ? 'xPadding' : 'yPadding'] + me.getBarGirth() / 2);
66193         return me.column ? [gutter, 0] : [0, gutter];
66194     },
66195
66196     // @private Get chart and data boundaries
66197     getBounds: function() {
66198         var me = this,
66199             chart = me.chart,
66200             store = chart.substore || chart.store,
66201             bars = [].concat(me.yField),
66202             barsLen = bars.length,
66203             groupBarsLen = barsLen,
66204             groupGutter = me.groupGutter / 100,
66205             column = me.column,
66206             xPadding = me.xPadding,
66207             yPadding = me.yPadding,
66208             stacked = me.stacked,
66209             barWidth = me.getBarGirth(),
66210             math = Math,
66211             mmax = math.max,
66212             mabs = math.abs,
66213             groupBarWidth, bbox, minY, maxY, axis, out,
66214             scale, zero, total, rec, j, plus, minus;
66215
66216         me.setBBox(true);
66217         bbox = me.bbox;
66218
66219         //Skip excluded series
66220         if (me.__excludes) {
66221             for (j = 0, total = me.__excludes.length; j < total; j++) {
66222                 if (me.__excludes[j]) {
66223                     groupBarsLen--;
66224                 }
66225             }
66226         }
66227
66228         if (me.axis) {
66229             axis = chart.axes.get(me.axis);
66230             if (axis) {
66231                 out = axis.calcEnds();
66232                 minY = out.from || axis.prevMin;
66233                 maxY = mmax(out.to || axis.prevMax, 0);
66234             }
66235         }
66236
66237         if (me.yField && !Ext.isNumber(minY)) {
66238             axis = Ext.create('Ext.chart.axis.Axis', {
66239                 chart: chart,
66240                 fields: [].concat(me.yField)
66241             });
66242             out = axis.calcEnds();
66243             minY = out.from || axis.prevMin;
66244             maxY = mmax(out.to || axis.prevMax, 0);
66245         }
66246
66247         if (!Ext.isNumber(minY)) {
66248             minY = 0;
66249         }
66250         if (!Ext.isNumber(maxY)) {
66251             maxY = 0;
66252         }
66253         scale = (column ? bbox.height - yPadding * 2 : bbox.width - xPadding * 2) / (maxY - minY);
66254         groupBarWidth = barWidth / ((stacked ? 1 : groupBarsLen) * (groupGutter + 1) - groupGutter);
66255         zero = (column) ? bbox.y + bbox.height - yPadding : bbox.x + xPadding;
66256
66257         if (stacked) {
66258             total = [[], []];
66259             store.each(function(record, i) {
66260                 total[0][i] = total[0][i] || 0;
66261                 total[1][i] = total[1][i] || 0;
66262                 for (j = 0; j < barsLen; j++) {
66263                     if (me.__excludes && me.__excludes[j]) {
66264                         continue;
66265                     }
66266                     rec = record.get(bars[j]);
66267                     total[+(rec > 0)][i] += mabs(rec);
66268                 }
66269             });
66270             total[+(maxY > 0)].push(mabs(maxY));
66271             total[+(minY > 0)].push(mabs(minY));
66272             minus = mmax.apply(math, total[0]);
66273             plus = mmax.apply(math, total[1]);
66274             scale = (column ? bbox.height - yPadding * 2 : bbox.width - xPadding * 2) / (plus + minus);
66275             zero = zero + minus * scale * (column ? -1 : 1);
66276         }
66277         else if (minY / maxY < 0) {
66278             zero = zero - minY * scale * (column ? -1 : 1);
66279         }
66280         return {
66281             bars: bars,
66282             bbox: bbox,
66283             barsLen: barsLen,
66284             groupBarsLen: groupBarsLen,
66285             barWidth: barWidth,
66286             groupBarWidth: groupBarWidth,
66287             scale: scale,
66288             zero: zero,
66289             xPadding: xPadding,
66290             yPadding: yPadding,
66291             signed: minY / maxY < 0,
66292             minY: minY,
66293             maxY: maxY
66294         };
66295     },
66296
66297     // @private Build an array of paths for the chart
66298     getPaths: function() {
66299         var me = this,
66300             chart = me.chart,
66301             store = chart.substore || chart.store,
66302             bounds = me.bounds = me.getBounds(),
66303             items = me.items = [],
66304             gutter = me.gutter / 100,
66305             groupGutter = me.groupGutter / 100,
66306             animate = chart.animate,
66307             column = me.column,
66308             group = me.group,
66309             enableShadows = chart.shadow,
66310             shadowGroups = me.shadowGroups,
66311             shadowAttributes = me.shadowAttributes,
66312             shadowGroupsLn = shadowGroups.length,
66313             bbox = bounds.bbox,
66314             xPadding = me.xPadding,
66315             yPadding = me.yPadding,
66316             stacked = me.stacked,
66317             barsLen = bounds.barsLen,
66318             colors = me.colorArrayStyle,
66319             colorLength = colors && colors.length || 0,
66320             math = Math,
66321             mmax = math.max,
66322             mmin = math.min,
66323             mabs = math.abs,
66324             j, yValue, height, totalDim, totalNegDim, bottom, top, hasShadow, barAttr, attrs, counter,
66325             shadowIndex, shadow, sprite, offset, floorY;
66326
66327         store.each(function(record, i, total) {
66328             bottom = bounds.zero;
66329             top = bounds.zero;
66330             totalDim = 0;
66331             totalNegDim = 0;
66332             hasShadow = false; 
66333             for (j = 0, counter = 0; j < barsLen; j++) {
66334                 // Excluded series
66335                 if (me.__excludes && me.__excludes[j]) {
66336                     continue;
66337                 }
66338                 yValue = record.get(bounds.bars[j]);
66339                 height = Math.round((yValue - ((bounds.minY < 0) ? 0 : bounds.minY)) * bounds.scale);
66340                 barAttr = {
66341                     fill: colors[(barsLen > 1 ? j : 0) % colorLength]
66342                 };
66343                 if (column) {
66344                     Ext.apply(barAttr, {
66345                         height: height,
66346                         width: mmax(bounds.groupBarWidth, 0),
66347                         x: (bbox.x + xPadding + i * bounds.barWidth * (1 + gutter) + counter * bounds.groupBarWidth * (1 + groupGutter) * !stacked),
66348                         y: bottom - height
66349                     });
66350                 }
66351                 else {
66352                     // draw in reverse order
66353                     offset = (total - 1) - i;
66354                     Ext.apply(barAttr, {
66355                         height: mmax(bounds.groupBarWidth, 0),
66356                         width: height + (bottom == bounds.zero),
66357                         x: bottom + (bottom != bounds.zero),
66358                         y: (bbox.y + yPadding + offset * bounds.barWidth * (1 + gutter) + counter * bounds.groupBarWidth * (1 + groupGutter) * !stacked + 1)
66359                     });
66360                 }
66361                 if (height < 0) {
66362                     if (column) {
66363                         barAttr.y = top;
66364                         barAttr.height = mabs(height);
66365                     } else {
66366                         barAttr.x = top + height;
66367                         barAttr.width = mabs(height);
66368                     }
66369                 }
66370                 if (stacked) {
66371                     if (height < 0) {
66372                         top += height * (column ? -1 : 1);
66373                     } else {
66374                         bottom += height * (column ? -1 : 1);
66375                     }
66376                     totalDim += mabs(height);
66377                     if (height < 0) {
66378                         totalNegDim += mabs(height);
66379                     }
66380                 }
66381                 barAttr.x = Math.floor(barAttr.x) + 1;
66382                 floorY = Math.floor(barAttr.y);
66383                 if (!Ext.isIE9 && barAttr.y > floorY) {
66384                     floorY--;
66385                 }
66386                 barAttr.y = floorY;
66387                 barAttr.width = Math.floor(barAttr.width);
66388                 barAttr.height = Math.floor(barAttr.height);
66389                 items.push({
66390                     series: me,
66391                     storeItem: record,
66392                     value: [record.get(me.xField), yValue],
66393                     attr: barAttr,
66394                     point: column ? [barAttr.x + barAttr.width / 2, yValue >= 0 ? barAttr.y : barAttr.y + barAttr.height] :
66395                                     [yValue >= 0 ? barAttr.x + barAttr.width : barAttr.x, barAttr.y + barAttr.height / 2]
66396                 });
66397                 // When resizing, reset before animating
66398                 if (animate && chart.resizing) {
66399                     attrs = column ? {
66400                         x: barAttr.x,
66401                         y: bounds.zero,
66402                         width: barAttr.width,
66403                         height: 0
66404                     } : {
66405                         x: bounds.zero,
66406                         y: barAttr.y,
66407                         width: 0,
66408                         height: barAttr.height
66409                     };
66410                     if (enableShadows && (stacked && !hasShadow || !stacked)) {
66411                         hasShadow = true;
66412                         //update shadows
66413                         for (shadowIndex = 0; shadowIndex < shadowGroupsLn; shadowIndex++) {
66414                             shadow = shadowGroups[shadowIndex].getAt(stacked ? i : (i * barsLen + j));
66415                             if (shadow) {
66416                                 shadow.setAttributes(attrs, true);
66417                             }
66418                         }
66419                     }
66420                     //update sprite position and width/height
66421                     sprite = group.getAt(i * barsLen + j);
66422                     if (sprite) {
66423                         sprite.setAttributes(attrs, true);
66424                     }
66425                 }
66426                 counter++;
66427             }
66428             if (stacked && items.length) {
66429                 items[i * counter].totalDim = totalDim;
66430                 items[i * counter].totalNegDim = totalNegDim;
66431             }
66432         }, me);
66433     },
66434
66435     // @private render/setAttributes on the shadows
66436     renderShadows: function(i, barAttr, baseAttrs, bounds) {
66437         var me = this,
66438             chart = me.chart,
66439             surface = chart.surface,
66440             animate = chart.animate,
66441             stacked = me.stacked,
66442             shadowGroups = me.shadowGroups,
66443             shadowAttributes = me.shadowAttributes,
66444             shadowGroupsLn = shadowGroups.length,
66445             store = chart.substore || chart.store,
66446             column = me.column,
66447             items = me.items,
66448             shadows = [],
66449             zero = bounds.zero,
66450             shadowIndex, shadowBarAttr, shadow, totalDim, totalNegDim, j, rendererAttributes;
66451
66452         if ((stacked && (i % bounds.groupBarsLen === 0)) || !stacked) {
66453             j = i / bounds.groupBarsLen;
66454             //create shadows
66455             for (shadowIndex = 0; shadowIndex < shadowGroupsLn; shadowIndex++) {
66456                 shadowBarAttr = Ext.apply({}, shadowAttributes[shadowIndex]);
66457                 shadow = shadowGroups[shadowIndex].getAt(stacked ? j : i);
66458                 Ext.copyTo(shadowBarAttr, barAttr, 'x,y,width,height');
66459                 if (!shadow) {
66460                     shadow = surface.add(Ext.apply({
66461                         type: 'rect',
66462                         group: shadowGroups[shadowIndex]
66463                     }, Ext.apply({}, baseAttrs, shadowBarAttr)));
66464                 }
66465                 if (stacked) {
66466                     totalDim = items[i].totalDim;
66467                     totalNegDim = items[i].totalNegDim;
66468                     if (column) {
66469                         shadowBarAttr.y = zero - totalNegDim;
66470                         shadowBarAttr.height = totalDim;
66471                     }
66472                     else {
66473                         shadowBarAttr.x = zero - totalNegDim;
66474                         shadowBarAttr.width = totalDim;
66475                     }
66476                 }
66477                 if (animate) {
66478                     if (!stacked) {
66479                         rendererAttributes = me.renderer(shadow, store.getAt(j), shadowBarAttr, i, store);
66480                         me.onAnimate(shadow, { to: rendererAttributes });
66481                     }
66482                     else {
66483                         rendererAttributes = me.renderer(shadow, store.getAt(j), Ext.apply(shadowBarAttr, { hidden: true }), i, store);
66484                         shadow.setAttributes(rendererAttributes, true);
66485                     }
66486                 }
66487                 else {
66488                     rendererAttributes = me.renderer(shadow, store.getAt(j), Ext.apply(shadowBarAttr, { hidden: false }), i, store);
66489                     shadow.setAttributes(rendererAttributes, true);
66490                 }
66491                 shadows.push(shadow);
66492             }
66493         }
66494         return shadows;
66495     },
66496
66497     /**
66498      * Draws the series for the current chart.
66499      */
66500     drawSeries: function() {
66501         var me = this,
66502             chart = me.chart,
66503             store = chart.substore || chart.store,
66504             surface = chart.surface,
66505             animate = chart.animate,
66506             stacked = me.stacked,
66507             column = me.column,
66508             enableShadows = chart.shadow,
66509             shadowGroups = me.shadowGroups,
66510             shadowGroupsLn = shadowGroups.length,
66511             group = me.group,
66512             seriesStyle = me.seriesStyle,
66513             items, ln, i, j, baseAttrs, sprite, rendererAttributes, shadowIndex, shadowGroup,
66514             bounds, endSeriesStyle, barAttr, attrs, anim;
66515         
66516         if (!store || !store.getCount()) {
66517             return;
66518         }
66519         
66520         //fill colors are taken from the colors array.
66521         delete seriesStyle.fill;
66522         endSeriesStyle = Ext.apply(seriesStyle, this.style);
66523         me.unHighlightItem();
66524         me.cleanHighlights();
66525
66526         me.getPaths();
66527         bounds = me.bounds;
66528         items = me.items;
66529
66530         baseAttrs = column ? {
66531             y: bounds.zero,
66532             height: 0
66533         } : {
66534             x: bounds.zero,
66535             width: 0
66536         };
66537         ln = items.length;
66538         // Create new or reuse sprites and animate/display
66539         for (i = 0; i < ln; i++) {
66540             sprite = group.getAt(i);
66541             barAttr = items[i].attr;
66542
66543             if (enableShadows) {
66544                 items[i].shadows = me.renderShadows(i, barAttr, baseAttrs, bounds);
66545             }
66546
66547             // Create a new sprite if needed (no height)
66548             if (!sprite) {
66549                 attrs = Ext.apply({}, baseAttrs, barAttr);
66550                 attrs = Ext.apply(attrs, endSeriesStyle || {});
66551                 sprite = surface.add(Ext.apply({}, {
66552                     type: 'rect',
66553                     group: group
66554                 }, attrs));
66555             }
66556             if (animate) {
66557                 rendererAttributes = me.renderer(sprite, store.getAt(i), barAttr, i, store);
66558                 sprite._to = rendererAttributes;
66559                 anim = me.onAnimate(sprite, { to: Ext.apply(rendererAttributes, endSeriesStyle) });
66560                 if (enableShadows && stacked && (i % bounds.barsLen === 0)) {
66561                     j = i / bounds.barsLen;
66562                     for (shadowIndex = 0; shadowIndex < shadowGroupsLn; shadowIndex++) {
66563                         anim.on('afteranimate', function() {
66564                             this.show(true);
66565                         }, shadowGroups[shadowIndex].getAt(j));
66566                     }
66567                 }
66568             }
66569             else {
66570                 rendererAttributes = me.renderer(sprite, store.getAt(i), Ext.apply(barAttr, { hidden: false }), i, store);
66571                 sprite.setAttributes(Ext.apply(rendererAttributes, endSeriesStyle), true);
66572             }
66573             items[i].sprite = sprite;
66574         }
66575
66576         // Hide unused sprites
66577         ln = group.getCount();
66578         for (j = i; j < ln; j++) {
66579             group.getAt(j).hide(true);
66580         }
66581         // Hide unused shadows
66582         if (enableShadows) {
66583             for (shadowIndex = 0; shadowIndex < shadowGroupsLn; shadowIndex++) {
66584                 shadowGroup = shadowGroups[shadowIndex];
66585                 ln = shadowGroup.getCount();
66586                 for (j = i; j < ln; j++) {
66587                     shadowGroup.getAt(j).hide(true);
66588                 }
66589             }
66590         }
66591         me.renderLabels();
66592     },
66593     
66594     // @private handled when creating a label.
66595     onCreateLabel: function(storeItem, item, i, display) {
66596         var me = this,
66597             surface = me.chart.surface,
66598             group = me.labelsGroup,
66599             config = me.label,
66600             endLabelStyle = Ext.apply({}, config, me.seriesLabelStyle || {}),
66601             sprite;
66602         return surface.add(Ext.apply({
66603             type: 'text',
66604             group: group
66605         }, endLabelStyle || {}));
66606     },
66607     
66608     // @private callback used when placing a label.
66609     onPlaceLabel: function(label, storeItem, item, i, display, animate, index) {
66610         // Determine the label's final position. Starts with the configured preferred value but
66611         // may get flipped from inside to outside or vice-versa depending on space.
66612         var me = this,
66613             opt = me.bounds,
66614             groupBarWidth = opt.groupBarWidth,
66615             column = me.column,
66616             chart = me.chart,
66617             chartBBox = chart.chartBBox,
66618             resizing = chart.resizing,
66619             xValue = item.value[0],
66620             yValue = item.value[1],
66621             attr = item.attr,
66622             config = me.label,
66623             rotate = config.orientation == 'vertical',
66624             field = [].concat(config.field),
66625             format = config.renderer,
66626             text = format(storeItem.get(field[index])),
66627             size = me.getLabelSize(text),
66628             width = size.width,
66629             height = size.height,
66630             zero = opt.zero,
66631             outside = 'outside',
66632             insideStart = 'insideStart',
66633             insideEnd = 'insideEnd',
66634             offsetX = 10,
66635             offsetY = 6,
66636             signed = opt.signed,
66637             x, y, finalAttr;
66638
66639         label.setAttributes({
66640             text: text
66641         });
66642
66643         if (column) {
66644             if (display == outside) {
66645                 if (height + offsetY + attr.height > (yValue >= 0 ? zero - chartBBox.y : chartBBox.y + chartBBox.height - zero)) {
66646                     display = insideEnd;
66647                 }
66648             } else {
66649                 if (height + offsetY > attr.height) {
66650                     display = outside;
66651                 }
66652             }
66653             x = attr.x + groupBarWidth / 2;
66654             y = display == insideStart ?
66655                     (zero + ((height / 2 + 3) * (yValue >= 0 ? -1 : 1))) :
66656                     (yValue >= 0 ? (attr.y + ((height / 2 + 3) * (display == outside ? -1 : 1))) :
66657                                    (attr.y + attr.height + ((height / 2 + 3) * (display === outside ? 1 : -1))));
66658         }
66659         else {
66660             if (display == outside) {
66661                 if (width + offsetX + attr.width > (yValue >= 0 ? chartBBox.x + chartBBox.width - zero : zero - chartBBox.x)) {
66662                     display = insideEnd;
66663                 }
66664             }
66665             else {
66666                 if (width + offsetX > attr.width) {
66667                     display = outside;
66668                 }
66669             }
66670             x = display == insideStart ?
66671                 (zero + ((width / 2 + 5) * (yValue >= 0 ? 1 : -1))) :
66672                 (yValue >= 0 ? (attr.x + attr.width + ((width / 2 + 5) * (display === outside ? 1 : -1))) :
66673                 (attr.x + ((width / 2 + 5) * (display === outside ? -1 : 1))));
66674             y = attr.y + groupBarWidth / 2;
66675         }
66676         //set position
66677         finalAttr = {
66678             x: x,
66679             y: y
66680         };
66681         //rotate
66682         if (rotate) {
66683             finalAttr.rotate = {
66684                 x: x,
66685                 y: y,
66686                 degrees: 270
66687             };
66688         }
66689         //check for resizing
66690         if (animate && resizing) {
66691             if (column) {
66692                 x = attr.x + attr.width / 2;
66693                 y = zero;
66694             } else {
66695                 x = zero;
66696                 y = attr.y + attr.height / 2;
66697             }
66698             label.setAttributes({
66699                 x: x,
66700                 y: y
66701             }, true);
66702             if (rotate) {
66703                 label.setAttributes({
66704                     rotate: {
66705                         x: x,
66706                         y: y,
66707                         degrees: 270
66708                     }
66709                 }, true);
66710             }
66711         }
66712         //handle animation
66713         if (animate) {
66714             me.onAnimate(label, { to: finalAttr });
66715         }
66716         else {
66717             label.setAttributes(Ext.apply(finalAttr, {
66718                 hidden: false
66719             }), true);
66720         }
66721     },
66722
66723     /* @private
66724      * Gets the dimensions of a given bar label. Uses a single hidden sprite to avoid
66725      * changing visible sprites.
66726      * @param value
66727      */
66728     getLabelSize: function(value) {
66729         var tester = this.testerLabel,
66730             config = this.label,
66731             endLabelStyle = Ext.apply({}, config, this.seriesLabelStyle || {}),
66732             rotated = config.orientation === 'vertical',
66733             bbox, w, h,
66734             undef;
66735         if (!tester) {
66736             tester = this.testerLabel = this.chart.surface.add(Ext.apply({
66737                 type: 'text',
66738                 opacity: 0
66739             }, endLabelStyle));
66740         }
66741         tester.setAttributes({
66742             text: value
66743         }, true);
66744
66745         // Flip the width/height if rotated, as getBBox returns the pre-rotated dimensions
66746         bbox = tester.getBBox();
66747         w = bbox.width;
66748         h = bbox.height;
66749         return {
66750             width: rotated ? h : w,
66751             height: rotated ? w : h
66752         };
66753     },
66754
66755     // @private used to animate label, markers and other sprites.
66756     onAnimate: function(sprite, attr) {
66757         sprite.show();
66758         return this.callParent(arguments);
66759     },
66760     
66761     isItemInPoint: function(x, y, item) {
66762         var bbox = item.sprite.getBBox();
66763         return bbox.x <= x && bbox.y <= y
66764             && (bbox.x + bbox.width) >= x
66765             && (bbox.y + bbox.height) >= y;
66766     },
66767     
66768     // @private hide all markers
66769     hideAll: function() {
66770         var axes = this.chart.axes;
66771         if (!isNaN(this._index)) {
66772             if (!this.__excludes) {
66773                 this.__excludes = [];
66774             }
66775             this.__excludes[this._index] = true;
66776             this.drawSeries();
66777             axes.each(function(axis) {
66778                 axis.drawAxis();
66779             });
66780         }
66781     },
66782
66783     // @private show all markers
66784     showAll: function() {
66785         var axes = this.chart.axes;
66786         if (!isNaN(this._index)) {
66787             if (!this.__excludes) {
66788                 this.__excludes = [];
66789             }
66790             this.__excludes[this._index] = false;
66791             this.drawSeries();
66792             axes.each(function(axis) {
66793                 axis.drawAxis();
66794             });
66795         }
66796     },
66797     
66798     /**
66799      * Returns a string with the color to be used for the series legend item.
66800      * @param index
66801      */
66802     getLegendColor: function(index) {
66803         var me = this;
66804         return me.colorArrayStyle[index % me.colorArrayStyle.length];
66805     }
66806 });
66807 /**
66808  * @class Ext.chart.series.Column
66809  * @extends Ext.chart.series.Bar
66810  * 
66811   <p>
66812   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 
66813   categories that can show some progression (or regression) in the data set.
66814   As with all other series, the Column Series must be appended in the *series* Chart array configuration. See the Chart 
66815   documentation for more information. A typical configuration object for the column series could be:
66816   </p>
66817 {@img Ext.chart.series.Column/Ext.chart.series.Column.png Ext.chart.series.Column chart series  
66818   <pre><code>
66819     var store = Ext.create('Ext.data.JsonStore', {
66820         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
66821         data: [
66822             {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
66823             {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
66824             {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
66825             {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
66826             {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}                                                
66827         ]
66828     });
66829     
66830     Ext.create('Ext.chart.Chart', {
66831         renderTo: Ext.getBody(),
66832         width: 500,
66833         height: 300,
66834         animate: true,
66835         store: store,
66836         axes: [{
66837             type: 'Numeric',
66838             position: 'bottom',
66839             fields: ['data1'],
66840             label: {
66841                 renderer: Ext.util.Format.numberRenderer('0,0')
66842             },
66843             title: 'Sample Values',
66844             grid: true,
66845             minimum: 0
66846         }, {
66847             type: 'Category',
66848             position: 'left',
66849             fields: ['name'],
66850             title: 'Sample Metrics'
66851         }],
66852             axes: [{
66853                 type: 'Numeric',
66854                 position: 'left',
66855                 fields: ['data1'],
66856                 label: {
66857                     renderer: Ext.util.Format.numberRenderer('0,0')
66858                 },
66859                 title: 'Sample Values',
66860                 grid: true,
66861                 minimum: 0
66862             }, {
66863                 type: 'Category',
66864                 position: 'bottom',
66865                 fields: ['name'],
66866                 title: 'Sample Metrics'
66867             }],
66868             series: [{
66869                 type: 'column',
66870                 axis: 'left',
66871                 highlight: true,
66872                 tips: {
66873                   trackMouse: true,
66874                   width: 140,
66875                   height: 28,
66876                   renderer: function(storeItem, item) {
66877                     this.setTitle(storeItem.get('name') + ': ' + storeItem.get('data1') + ' $');
66878                   }
66879                 },
66880                 label: {
66881                   display: 'insideEnd',
66882                   'text-anchor': 'middle',
66883                     field: 'data1',
66884                     renderer: Ext.util.Format.numberRenderer('0'),
66885                     orientation: 'vertical',
66886                     color: '#333'
66887                 },
66888                 xField: 'name',
66889                 yField: 'data1'
66890             }]
66891     });
66892    </code></pre>
66893  
66894   <p>
66895   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
66896   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. 
66897   </p>
66898  */
66899
66900 Ext.define('Ext.chart.series.Column', {
66901
66902     /* Begin Definitions */
66903
66904     alternateClassName: ['Ext.chart.ColumnSeries', 'Ext.chart.ColumnChart', 'Ext.chart.StackedColumnChart'],
66905
66906     extend: 'Ext.chart.series.Bar',
66907
66908     /* End Definitions */
66909
66910     type: 'column',
66911     alias: 'series.column',
66912
66913     column: true,
66914
66915     /**
66916      * @cfg {Number} xPadding
66917      * Padding between the left/right axes and the bars
66918      */
66919     xPadding: 10,
66920
66921     /**
66922      * @cfg {Number} yPadding
66923      * Padding between the top/bottom axes and the bars
66924      */
66925     yPadding: 0
66926 });
66927 /**
66928  * @class Ext.chart.series.Gauge
66929  * @extends Ext.chart.series.Series
66930  * 
66931  * Creates a Gauge Chart. Gauge Charts are used to show progress in a certain variable. There are two ways of using the Gauge chart.
66932  * One is setting a store element into the Gauge and selecting the field to be used from that store. Another one is instanciating the
66933  * visualization and using the `setValue` method to adjust the value you want.
66934  *
66935  * A chart/series configuration for the Gauge visualization could look like this:
66936  * 
66937  *     {
66938  *         xtype: 'chart',
66939  *         store: store,
66940  *         axes: [{
66941  *             type: 'gauge',
66942  *             position: 'gauge',
66943  *             minimum: 0,
66944  *             maximum: 100,
66945  *             steps: 10,
66946  *             margin: -10
66947  *         }],
66948  *         series: [{
66949  *             type: 'gauge',
66950  *             field: 'data1',
66951  *             donut: false,
66952  *             colorSet: ['#F49D10', '#ddd']
66953  *         }]
66954  *     }
66955  * 
66956  * In this configuration we create a special Gauge axis to be used with the gauge visualization (describing half-circle markers), and also we're
66957  * setting a maximum, minimum and steps configuration options into the axis. The Gauge series configuration contains the store field to be bound to
66958  * the visual display and the color set to be used with the visualization.
66959  * 
66960  * @xtype gauge
66961  */
66962 Ext.define('Ext.chart.series.Gauge', {
66963
66964     /* Begin Definitions */
66965
66966     extend: 'Ext.chart.series.Series',
66967
66968     /* End Definitions */
66969
66970     type: "gauge",
66971     alias: 'series.gauge',
66972
66973     rad: Math.PI / 180,
66974
66975     /**
66976      * @cfg {Number} highlightDuration
66977      * The duration for the pie slice highlight effect.
66978      */
66979     highlightDuration: 150,
66980
66981     /**
66982      * @cfg {String} angleField
66983      * The store record field name to be used for the pie angles.
66984      * The values bound to this field name must be positive real numbers.
66985      * This parameter is required.
66986      */
66987     angleField: false,
66988
66989     /**
66990      * @cfg {Boolean} needle
66991      * Use the Gauge Series as an area series or add a needle to it. Default's false.
66992      */
66993     needle: false,
66994     
66995     /**
66996      * @cfg {Boolean|Number} donut
66997      * Use the entire disk or just a fraction of it for the gauge. Default's false.
66998      */
66999     donut: false,
67000
67001     /**
67002      * @cfg {Boolean} showInLegend
67003      * Whether to add the pie chart elements as legend items. Default's false.
67004      */
67005     showInLegend: false,
67006
67007     /**
67008      * @cfg {Object} style
67009      * An object containing styles for overriding series styles from Theming.
67010      */
67011     style: {},
67012     
67013     constructor: function(config) {
67014         this.callParent(arguments);
67015         var me = this,
67016             chart = me.chart,
67017             surface = chart.surface,
67018             store = chart.store,
67019             shadow = chart.shadow, i, l, cfg;
67020         Ext.apply(me, config, {
67021             shadowAttributes: [{
67022                 "stroke-width": 6,
67023                 "stroke-opacity": 1,
67024                 stroke: 'rgb(200, 200, 200)',
67025                 translate: {
67026                     x: 1.2,
67027                     y: 2
67028                 }
67029             },
67030             {
67031                 "stroke-width": 4,
67032                 "stroke-opacity": 1,
67033                 stroke: 'rgb(150, 150, 150)',
67034                 translate: {
67035                     x: 0.9,
67036                     y: 1.5
67037                 }
67038             },
67039             {
67040                 "stroke-width": 2,
67041                 "stroke-opacity": 1,
67042                 stroke: 'rgb(100, 100, 100)',
67043                 translate: {
67044                     x: 0.6,
67045                     y: 1
67046                 }
67047             }]
67048         });
67049         me.group = surface.getGroup(me.seriesId);
67050         if (shadow) {
67051             for (i = 0, l = me.shadowAttributes.length; i < l; i++) {
67052                 me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
67053             }
67054         }
67055         surface.customAttributes.segment = function(opt) {
67056             return me.getSegment(opt);
67057         };
67058     },
67059     
67060     //@private updates some onbefore render parameters.
67061     initialize: function() {
67062         var me = this,
67063             store = me.chart.substore || me.chart.store;
67064         //Add yFields to be used in Legend.js
67065         me.yField = [];
67066         if (me.label.field) {
67067             store.each(function(rec) {
67068                 me.yField.push(rec.get(me.label.field));
67069             });
67070         }
67071     },
67072
67073     // @private returns an object with properties for a Slice
67074     getSegment: function(opt) {
67075         var me = this,
67076             rad = me.rad,
67077             cos = Math.cos,
67078             sin = Math.sin,
67079             abs = Math.abs,
67080             x = me.centerX,
67081             y = me.centerY,
67082             x1 = 0, x2 = 0, x3 = 0, x4 = 0,
67083             y1 = 0, y2 = 0, y3 = 0, y4 = 0,
67084             delta = 1e-2,
67085             r = opt.endRho - opt.startRho,
67086             startAngle = opt.startAngle,
67087             endAngle = opt.endAngle,
67088             midAngle = (startAngle + endAngle) / 2 * rad,
67089             margin = opt.margin || 0,
67090             flag = abs(endAngle - startAngle) > 180,
67091             a1 = Math.min(startAngle, endAngle) * rad,
67092             a2 = Math.max(startAngle, endAngle) * rad,
67093             singleSlice = false;
67094
67095         x += margin * cos(midAngle);
67096         y += margin * sin(midAngle);
67097
67098         x1 = x + opt.startRho * cos(a1);
67099         y1 = y + opt.startRho * sin(a1);
67100
67101         x2 = x + opt.endRho * cos(a1);
67102         y2 = y + opt.endRho * sin(a1);
67103
67104         x3 = x + opt.startRho * cos(a2);
67105         y3 = y + opt.startRho * sin(a2);
67106
67107         x4 = x + opt.endRho * cos(a2);
67108         y4 = y + opt.endRho * sin(a2);
67109
67110         if (abs(x1 - x3) <= delta && abs(y1 - y3) <= delta) {
67111             singleSlice = true;
67112         }
67113         //Solves mysterious clipping bug with IE
67114         if (singleSlice) {
67115             return {
67116                 path: [
67117                 ["M", x1, y1],
67118                 ["L", x2, y2],
67119                 ["A", opt.endRho, opt.endRho, 0, +flag, 1, x4, y4],
67120                 ["Z"]]
67121             };
67122         } else {
67123             return {
67124                 path: [
67125                 ["M", x1, y1],
67126                 ["L", x2, y2],
67127                 ["A", opt.endRho, opt.endRho, 0, +flag, 1, x4, y4],
67128                 ["L", x3, y3],
67129                 ["A", opt.startRho, opt.startRho, 0, +flag, 0, x1, y1],
67130                 ["Z"]]
67131             };
67132         }
67133     },
67134
67135     // @private utility function to calculate the middle point of a pie slice.
67136     calcMiddle: function(item) {
67137         var me = this,
67138             rad = me.rad,
67139             slice = item.slice,
67140             x = me.centerX,
67141             y = me.centerY,
67142             startAngle = slice.startAngle,
67143             endAngle = slice.endAngle,
67144             radius = Math.max(('rho' in slice) ? slice.rho: me.radius, me.label.minMargin),
67145             donut = +me.donut,
67146             a1 = Math.min(startAngle, endAngle) * rad,
67147             a2 = Math.max(startAngle, endAngle) * rad,
67148             midAngle = -(a1 + (a2 - a1) / 2),
67149             xm = x + (item.endRho + item.startRho) / 2 * Math.cos(midAngle),
67150             ym = y - (item.endRho + item.startRho) / 2 * Math.sin(midAngle);
67151
67152         item.middle = {
67153             x: xm,
67154             y: ym
67155         };
67156     },
67157
67158     /**
67159      * Draws the series for the current chart.
67160      */
67161     drawSeries: function() {
67162         var me = this,
67163             chart = me.chart,
67164             store = chart.substore || chart.store,
67165             group = me.group,
67166             animate = me.chart.animate,
67167             axis = me.chart.axes.get(0),
67168             minimum = axis && axis.minimum || me.minimum || 0,
67169             maximum = axis && axis.maximum || me.maximum || 0,
67170             field = me.angleField || me.field || me.xField,
67171             surface = chart.surface,
67172             chartBBox = chart.chartBBox,
67173             rad = me.rad,
67174             donut = +me.donut,
67175             values = {},
67176             items = [],
67177             seriesStyle = me.seriesStyle,
67178             seriesLabelStyle = me.seriesLabelStyle,
67179             colorArrayStyle = me.colorArrayStyle,
67180             colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
67181             gutterX = chart.maxGutter[0],
67182             gutterY = chart.maxGutter[1],
67183             cos = Math.cos,
67184             sin = Math.sin,
67185             rendererAttributes, centerX, centerY, slice, slices, sprite, value,
67186             item, ln, record, i, j, startAngle, endAngle, middleAngle, sliceLength, path,
67187             p, spriteOptions, bbox, splitAngle, sliceA, sliceB;
67188         
67189         Ext.apply(seriesStyle, me.style || {});
67190
67191         me.setBBox();
67192         bbox = me.bbox;
67193
67194         //override theme colors
67195         if (me.colorSet) {
67196             colorArrayStyle = me.colorSet;
67197             colorArrayLength = colorArrayStyle.length;
67198         }
67199         
67200         //if not store or store is empty then there's nothing to draw
67201         if (!store || !store.getCount()) {
67202             return;
67203         }
67204         
67205         centerX = me.centerX = chartBBox.x + (chartBBox.width / 2);
67206         centerY = me.centerY = chartBBox.y + chartBBox.height;
67207         me.radius = Math.min(centerX - chartBBox.x, centerY - chartBBox.y);
67208         me.slices = slices = [];
67209         me.items = items = [];
67210         
67211         if (!me.value) {
67212             record = store.getAt(0);
67213             me.value = record.get(field);
67214         }
67215         
67216         value = me.value;
67217         if (me.needle) {
67218             sliceA = {
67219                 series: me,
67220                 value: value,
67221                 startAngle: -180,
67222                 endAngle: 0,
67223                 rho: me.radius
67224             };
67225             splitAngle = -180 * (1 - (value - minimum) / (maximum - minimum));
67226             slices.push(sliceA);
67227         } else {
67228             splitAngle = -180 * (1 - (value - minimum) / (maximum - minimum));
67229             sliceA = {
67230                 series: me,
67231                 value: value,
67232                 startAngle: -180,
67233                 endAngle: splitAngle,
67234                 rho: me.radius
67235             };
67236             sliceB = {
67237                 series: me,
67238                 value: me.maximum - value,
67239                 startAngle: splitAngle,
67240                 endAngle: 0,
67241                 rho: me.radius
67242             };
67243             slices.push(sliceA, sliceB);
67244         }
67245         
67246         //do pie slices after.
67247         for (i = 0, ln = slices.length; i < ln; i++) {
67248             slice = slices[i];
67249             sprite = group.getAt(i);
67250             //set pie slice properties
67251             rendererAttributes = Ext.apply({
67252                 segment: {
67253                     startAngle: slice.startAngle,
67254                     endAngle: slice.endAngle,
67255                     margin: 0,
67256                     rho: slice.rho,
67257                     startRho: slice.rho * +donut / 100,
67258                     endRho: slice.rho
67259                 } 
67260             }, Ext.apply(seriesStyle, colorArrayStyle && { fill: colorArrayStyle[i % colorArrayLength] } || {}));
67261
67262             item = Ext.apply({},
67263             rendererAttributes.segment, {
67264                 slice: slice,
67265                 series: me,
67266                 storeItem: record,
67267                 index: i
67268             });
67269             items[i] = item;
67270             // Create a new sprite if needed (no height)
67271             if (!sprite) {
67272                 spriteOptions = Ext.apply({
67273                     type: "path",
67274                     group: group
67275                 }, Ext.apply(seriesStyle, colorArrayStyle && { fill: colorArrayStyle[i % colorArrayLength] } || {}));
67276                 sprite = surface.add(Ext.apply(spriteOptions, rendererAttributes));
67277             }
67278             slice.sprite = slice.sprite || [];
67279             item.sprite = sprite;
67280             slice.sprite.push(sprite);
67281             if (animate) {
67282                 rendererAttributes = me.renderer(sprite, record, rendererAttributes, i, store);
67283                 sprite._to = rendererAttributes;
67284                 me.onAnimate(sprite, {
67285                     to: rendererAttributes
67286                 });
67287             } else {
67288                 rendererAttributes = me.renderer(sprite, record, Ext.apply(rendererAttributes, {
67289                     hidden: false
67290                 }), i, store);
67291                 sprite.setAttributes(rendererAttributes, true);
67292             }
67293         }
67294         
67295         if (me.needle) {
67296             splitAngle = splitAngle * Math.PI / 180;
67297             
67298             if (!me.needleSprite) {
67299                 me.needleSprite = me.chart.surface.add({
67300                     type: 'path',
67301                     path: ['M', centerX + (me.radius * +donut / 100) * cos(splitAngle),
67302                                 centerY + -Math.abs((me.radius * +donut / 100) * sin(splitAngle)),
67303                            'L', centerX + me.radius * cos(splitAngle),
67304                                 centerY + -Math.abs(me.radius * sin(splitAngle))],
67305                     'stroke-width': 4,
67306                     'stroke': '#222'
67307                 });
67308             } else {
67309                 if (animate) {
67310                     me.onAnimate(me.needleSprite, {
67311                         to: {
67312                         path: ['M', centerX + (me.radius * +donut / 100) * cos(splitAngle),
67313                                     centerY + -Math.abs((me.radius * +donut / 100) * sin(splitAngle)),
67314                                'L', centerX + me.radius * cos(splitAngle),
67315                                     centerY + -Math.abs(me.radius * sin(splitAngle))]
67316                         }
67317                     });
67318                 } else {
67319                     me.needleSprite.setAttributes({
67320                         type: 'path',
67321                         path: ['M', centerX + (me.radius * +donut / 100) * cos(splitAngle),
67322                                     centerY + -Math.abs((me.radius * +donut / 100) * sin(splitAngle)),
67323                                'L', centerX + me.radius * cos(splitAngle),
67324                                     centerY + -Math.abs(me.radius * sin(splitAngle))]
67325                     });
67326                 }
67327             }
67328             me.needleSprite.setAttributes({
67329                 hidden: false    
67330             }, true);
67331         }
67332         
67333         delete me.value;
67334     },
67335     
67336     /**
67337      * Sets the Gauge chart to the current specified value.
67338     */
67339     setValue: function (value) {
67340         this.value = value;
67341         this.drawSeries();
67342     },
67343
67344     // @private callback for when creating a label sprite.
67345     onCreateLabel: function(storeItem, item, i, display) {},
67346
67347     // @private callback for when placing a label sprite.
67348     onPlaceLabel: function(label, storeItem, item, i, display, animate, index) {},
67349
67350     // @private callback for when placing a callout.
67351     onPlaceCallout: function() {},
67352
67353     // @private handles sprite animation for the series.
67354     onAnimate: function(sprite, attr) {
67355         sprite.show();
67356         return this.callParent(arguments);
67357     },
67358
67359     isItemInPoint: function(x, y, item, i) {
67360         return false;
67361     },
67362     
67363     // @private shows all elements in the series.
67364     showAll: function() {
67365         if (!isNaN(this._index)) {
67366             this.__excludes[this._index] = false;
67367             this.drawSeries();
67368         }
67369     },
67370     
67371     /**
67372      * Returns the color of the series (to be displayed as color for the series legend item).
67373      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
67374      */
67375     getLegendColor: function(index) {
67376         var me = this;
67377         return me.colorArrayStyle[index % me.colorArrayStyle.length];
67378     }
67379 });
67380
67381
67382 /**
67383  * @class Ext.chart.series.Line
67384  * @extends Ext.chart.series.Cartesian
67385  * 
67386  <p> 
67387   Creates a Line Chart. A Line Chart is a useful visualization technique to display quantitative information for different 
67388   categories or other real values (as opposed to the bar chart), that can show some progression (or regression) in the dataset.
67389   As with all other series, the Line Series must be appended in the *series* Chart array configuration. See the Chart 
67390   documentation for more information. A typical configuration object for the line series could be:
67391  </p>
67392 {@img Ext.chart.series.Line/Ext.chart.series.Line.png Ext.chart.series.Line chart series}
67393   <pre><code>
67394     var store = Ext.create('Ext.data.JsonStore', {
67395         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
67396         data: [
67397             {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
67398             {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
67399             {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
67400             {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
67401             {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}                                                
67402         ]
67403     });
67404     
67405     Ext.create('Ext.chart.Chart', {
67406         renderTo: Ext.getBody(),
67407         width: 500,
67408         height: 300,
67409         animate: true,
67410         store: store,
67411         axes: [{
67412             type: 'Numeric',
67413             position: 'bottom',
67414             fields: ['data1'],
67415             label: {
67416                 renderer: Ext.util.Format.numberRenderer('0,0')
67417             },
67418             title: 'Sample Values',
67419             grid: true,
67420             minimum: 0
67421         }, {
67422             type: 'Category',
67423             position: 'left',
67424             fields: ['name'],
67425             title: 'Sample Metrics'
67426         }],
67427         series: [{
67428             type: 'line',
67429             highlight: {
67430                 size: 7,
67431                 radius: 7
67432             },
67433             axis: 'left',
67434             xField: 'name',
67435             yField: 'data1',
67436             markerCfg: {
67437                 type: 'cross',
67438                 size: 4,
67439                 radius: 4,
67440                 'stroke-width': 0
67441             }
67442         }, {
67443             type: 'line',
67444             highlight: {
67445                 size: 7,
67446                 radius: 7
67447             },
67448             axis: 'left',
67449             fill: true,
67450             xField: 'name',
67451             yField: 'data3',
67452             markerCfg: {
67453                 type: 'circle',
67454                 size: 4,
67455                 radius: 4,
67456                 'stroke-width': 0
67457             }
67458         }]
67459     });
67460    </code></pre>
67461  
67462  <p> 
67463   In this configuration we're adding two series (or lines), one bound to the `data1` property of the store and the other to `data3`. The type for both configurations is 
67464   `line`. The `xField` for both series is the same, the name propert of the store. Both line series share the same axis, the left axis. You can set particular marker 
67465   configuration by adding properties onto the markerConfig object. Both series have an object as highlight so that markers animate smoothly to the properties in highlight 
67466   when hovered. The second series has `fill=true` which means that the line will also have an area below it of the same color.
67467  </p>
67468  */
67469
67470 Ext.define('Ext.chart.series.Line', {
67471
67472     /* Begin Definitions */
67473
67474     extend: 'Ext.chart.series.Cartesian',
67475
67476     alternateClassName: ['Ext.chart.LineSeries', 'Ext.chart.LineChart'],
67477
67478     requires: ['Ext.chart.axis.Axis', 'Ext.chart.Shape', 'Ext.draw.Draw', 'Ext.fx.Anim'],
67479
67480     /* End Definitions */
67481
67482     type: 'line',
67483     
67484     alias: 'series.line',
67485
67486     /**
67487      * @cfg {Number} selectionTolerance
67488      * The offset distance from the cursor position to the line series to trigger events (then used for highlighting series, etc).
67489      */
67490     selectionTolerance: 20,
67491     
67492     /**
67493      * @cfg {Boolean} showMarkers
67494      * Whether markers should be displayed at the data points along the line. If true,
67495      * then the {@link #markerConfig} config item will determine the markers' styling.
67496      */
67497     showMarkers: true,
67498
67499     /**
67500      * @cfg {Object} markerConfig
67501      * The display style for the markers. Only used if {@link #showMarkers} is true.
67502      * The markerConfig is a configuration object containing the same set of properties defined in
67503      * the Sprite class. For example, if we were to set red circles as markers to the line series we could
67504      * pass the object:
67505      *
67506      <pre><code>
67507         markerConfig: {
67508             type: 'circle',
67509             radius: 4,
67510             'fill': '#f00'
67511         }
67512      </code></pre>
67513      
67514      */
67515     markerConfig: {},
67516
67517     /**
67518      * @cfg {Object} style
67519      * An object containing styles for the visualization lines. These styles will override the theme styles. 
67520      * Some options contained within the style object will are described next.
67521      */
67522     style: {},
67523     
67524     /**
67525      * @cfg {Boolean} smooth
67526      * If true, the line will be smoothed/rounded around its points, otherwise straight line
67527      * segments will be drawn. Defaults to false.
67528      */
67529     smooth: false,
67530
67531     /**
67532      * @cfg {Boolean} fill
67533      * If true, the area below the line will be filled in using the {@link #style.eefill} and
67534      * {@link #style.opacity} config properties. Defaults to false.
67535      */
67536     fill: false,
67537
67538     constructor: function(config) {
67539         this.callParent(arguments);
67540         var me = this,
67541             surface = me.chart.surface,
67542             shadow = me.chart.shadow,
67543             i, l;
67544         Ext.apply(me, config, {
67545             highlightCfg: {
67546                 'stroke-width': 3
67547             },
67548             shadowAttributes: [{
67549                 "stroke-width": 6,
67550                 "stroke-opacity": 0.05,
67551                 stroke: 'rgb(0, 0, 0)',
67552                 translate: {
67553                     x: 1,
67554                     y: 1
67555                 }
67556             }, {
67557                 "stroke-width": 4,
67558                 "stroke-opacity": 0.1,
67559                 stroke: 'rgb(0, 0, 0)',
67560                 translate: {
67561                     x: 1,
67562                     y: 1
67563                 }
67564             }, {
67565                 "stroke-width": 2,
67566                 "stroke-opacity": 0.15,
67567                 stroke: 'rgb(0, 0, 0)',
67568                 translate: {
67569                     x: 1,
67570                     y: 1
67571                 }
67572             }]
67573         });
67574         me.group = surface.getGroup(me.seriesId);
67575         if (me.showMarkers) {
67576             me.markerGroup = surface.getGroup(me.seriesId + '-markers');
67577         }
67578         if (shadow) {
67579             for (i = 0, l = this.shadowAttributes.length; i < l; i++) {
67580                 me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
67581             }
67582         }
67583     },
67584     
67585     // @private makes an average of points when there are more data points than pixels to be rendered.
67586     shrink: function(xValues, yValues, size) {
67587         // Start at the 2nd point...
67588         var len = xValues.length,
67589             ratio = Math.floor(len / size),
67590             i = 1,
67591             xSum = 0,
67592             ySum = 0,
67593             xRes = [xValues[0]],
67594             yRes = [yValues[0]];
67595         
67596         for (; i < len; ++i) {
67597             xSum += xValues[i] || 0;
67598             ySum += yValues[i] || 0;
67599             if (i % ratio == 0) {
67600                 xRes.push(xSum/ratio);
67601                 yRes.push(ySum/ratio);
67602                 xSum = 0;
67603                 ySum = 0;
67604             }
67605         }
67606         return {
67607             x: xRes,
67608             y: yRes
67609         };
67610     },
67611
67612     /**
67613      * Draws the series for the current chart.
67614      */
67615     drawSeries: function() {
67616         var me = this,
67617             chart = me.chart,
67618             store = chart.substore || chart.store,
67619             surface = chart.surface,
67620             chartBBox = chart.chartBBox,
67621             bbox = {},
67622             group = me.group,
67623             gutterX = chart.maxGutter[0],
67624             gutterY = chart.maxGutter[1],
67625             showMarkers = me.showMarkers,
67626             markerGroup = me.markerGroup,
67627             enableShadows = chart.shadow,
67628             shadowGroups = me.shadowGroups,
67629             shadowAttributes = this.shadowAttributes,
67630             lnsh = shadowGroups.length,
67631             dummyPath = ["M"],
67632             path = ["M"],
67633             markerIndex = chart.markerIndex,
67634             axes = [].concat(me.axis),
67635             shadowGroup,
67636             shadowBarAttr,
67637             xValues = [],
67638             yValues = [],
67639             onbreak = false,
67640             markerStyle = me.markerStyle,
67641             seriesStyle = me.seriesStyle,
67642             seriesLabelStyle = me.seriesLabelStyle,
67643             colorArrayStyle = me.colorArrayStyle,
67644             colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
67645             seriesIdx = me.seriesIdx, shadows, shadow, shindex, fromPath, fill, fillPath, rendererAttributes,
67646             x, y, prevX, prevY, firstY, markerCount, i, j, ln, axis, ends, marker, markerAux, item, xValue,
67647             yValue, coords, xScale, yScale, minX, maxX, minY, maxY, line, animation, endMarkerStyle,
67648             endLineStyle, type, props, firstMarker;
67649         
67650         //if store is empty then there's nothing to draw.
67651         if (!store || !store.getCount()) {
67652             return;
67653         }
67654         
67655         //prepare style objects for line and markers
67656         endMarkerStyle = Ext.apply(markerStyle, me.markerConfig);
67657         type = endMarkerStyle.type;
67658         delete endMarkerStyle.type;
67659         endLineStyle = Ext.apply(seriesStyle, me.style);
67660         //if no stroke with is specified force it to 0.5 because this is
67661         //about making *lines*
67662         if (!endLineStyle['stroke-width']) {
67663             endLineStyle['stroke-width'] = 0.5;
67664         }
67665         //If we're using a time axis and we need to translate the points,
67666         //then reuse the first markers as the last markers.
67667         if (markerIndex && markerGroup && markerGroup.getCount()) {
67668             for (i = 0; i < markerIndex; i++) {
67669                 marker = markerGroup.getAt(i);
67670                 markerGroup.remove(marker);
67671                 markerGroup.add(marker);
67672                 markerAux = markerGroup.getAt(markerGroup.getCount() - 2);
67673                 marker.setAttributes({
67674                     x: 0,
67675                     y: 0,
67676                     translate: {
67677                         x: markerAux.attr.translation.x,
67678                         y: markerAux.attr.translation.y
67679                     }
67680                 }, true);
67681             }
67682         }
67683         
67684         me.unHighlightItem();
67685         me.cleanHighlights();
67686
67687         me.setBBox();
67688         bbox = me.bbox;
67689
67690         me.clipRect = [bbox.x, bbox.y, bbox.width, bbox.height];
67691
67692         for (i = 0, ln = axes.length; i < ln; i++) { 
67693             axis = chart.axes.get(axes[i]);
67694             if (axis) {
67695                 ends = axis.calcEnds();
67696                 if (axis.position == 'top' || axis.position == 'bottom') {
67697                     minX = ends.from;
67698                     maxX = ends.to;
67699                 }
67700                 else {
67701                     minY = ends.from;
67702                     maxY = ends.to;
67703                 }
67704             }
67705         }
67706         // If a field was specified without a corresponding axis, create one to get bounds
67707         //only do this for the axis where real values are bound (that's why we check for
67708         //me.axis)
67709         if (me.xField && !Ext.isNumber(minX)
67710             && (me.axis == 'bottom' || me.axis == 'top')) {
67711             axis = Ext.create('Ext.chart.axis.Axis', {
67712                 chart: chart,
67713                 fields: [].concat(me.xField)
67714             }).calcEnds();
67715             minX = axis.from;
67716             maxX = axis.to;
67717         }
67718         if (me.yField && !Ext.isNumber(minY)
67719             && (me.axis == 'right' || me.axis == 'left')) {
67720             axis = Ext.create('Ext.chart.axis.Axis', {
67721                 chart: chart,
67722                 fields: [].concat(me.yField)
67723             }).calcEnds();
67724             minY = axis.from;
67725             maxY = axis.to;
67726         }
67727
67728         if (isNaN(minX)) {
67729             minX = 0;
67730             xScale = bbox.width / (store.getCount() - 1);
67731         }
67732         else {
67733             xScale = bbox.width / (maxX - minX);
67734         }
67735
67736         if (isNaN(minY)) {
67737             minY = 0;
67738             yScale = bbox.height / (store.getCount() - 1);
67739         } 
67740         else {
67741             yScale = bbox.height / (maxY - minY);
67742         }
67743
67744         store.each(function(record, i) {
67745             xValue = record.get(me.xField);
67746             yValue = record.get(me.yField);
67747             //skip undefined values
67748             if (typeof yValue == 'undefined' || (typeof yValue == 'string' && !yValue)) {
67749                 if (Ext.isDefined(Ext.global.console)) {
67750                     Ext.global.console.warn("[Ext.chart.series.Line]  Skipping a store element with an undefined value at ", record, xValue, yValue);
67751                 }
67752                 return;
67753             }
67754             // Ensure a value
67755             if (typeof xValue == 'string' || typeof xValue == 'object'
67756                 //set as uniform distribution if the axis is a category axis.
67757                 || (me.axis != 'top' && me.axis != 'bottom')) {
67758                 xValue = i;
67759             }
67760             if (typeof yValue == 'string' || typeof yValue == 'object'
67761                 //set as uniform distribution if the axis is a category axis.
67762                 || (me.axis != 'left' && me.axis != 'right')) {
67763                 yValue = i;
67764             }
67765             xValues.push(xValue);
67766             yValues.push(yValue);
67767         }, me);
67768
67769         ln = xValues.length;
67770         if (ln > bbox.width) {
67771             coords = me.shrink(xValues, yValues, bbox.width);
67772             xValues = coords.x;
67773             yValues = coords.y;
67774         }
67775
67776         me.items = [];
67777
67778         ln = xValues.length;
67779         for (i = 0; i < ln; i++) {
67780             xValue = xValues[i];
67781             yValue = yValues[i];
67782             if (yValue === false) {
67783                 if (path.length == 1) {
67784                     path = [];
67785                 }
67786                 onbreak = true;
67787                 me.items.push(false);
67788                 continue;
67789             } else {
67790                 x = (bbox.x + (xValue - minX) * xScale).toFixed(2);
67791                 y = ((bbox.y + bbox.height) - (yValue - minY) * yScale).toFixed(2);
67792                 if (onbreak) {
67793                     onbreak = false;
67794                     path.push('M');
67795                 } 
67796                 path = path.concat([x, y]);
67797             }
67798             if ((typeof firstY == 'undefined') && (typeof y != 'undefined')) {
67799                 firstY = y;
67800             }
67801             // If this is the first line, create a dummypath to animate in from.
67802             if (!me.line || chart.resizing) {
67803                 dummyPath = dummyPath.concat([x, bbox.y + bbox.height / 2]);
67804             }
67805
67806             // When resizing, reset before animating
67807             if (chart.animate && chart.resizing && me.line) {
67808                 me.line.setAttributes({
67809                     path: dummyPath
67810                 }, true);
67811                 if (me.fillPath) {
67812                     me.fillPath.setAttributes({
67813                         path: dummyPath,
67814                         opacity: 0.2
67815                     }, true);
67816                 }
67817                 if (me.line.shadows) {
67818                     shadows = me.line.shadows;
67819                     for (j = 0, lnsh = shadows.length; j < lnsh; j++) {
67820                         shadow = shadows[j];
67821                         shadow.setAttributes({
67822                             path: dummyPath
67823                         }, true);
67824                     }
67825                 }
67826             }
67827             if (showMarkers) {
67828                 marker = markerGroup.getAt(i);
67829                 if (!marker) {
67830                     marker = Ext.chart.Shape[type](surface, Ext.apply({
67831                         group: [group, markerGroup],
67832                         x: 0, y: 0,
67833                         translate: {
67834                             x: prevX || x, 
67835                             y: prevY || (bbox.y + bbox.height / 2)
67836                         },
67837                         value: '"' + xValue + ', ' + yValue + '"'
67838                     }, endMarkerStyle));
67839                     marker._to = {
67840                         translate: {
67841                             x: x,
67842                             y: y
67843                         }
67844                     };
67845                 } else {
67846                     marker.setAttributes({
67847                         value: '"' + xValue + ', ' + yValue + '"',
67848                         x: 0, y: 0,
67849                         hidden: false
67850                     }, true);
67851                     marker._to = {
67852                         translate: {
67853                             x: x, y: y
67854                         }
67855                     };
67856                 }
67857             }
67858             me.items.push({
67859                 series: me,
67860                 value: [xValue, yValue],
67861                 point: [x, y],
67862                 sprite: marker,
67863                 storeItem: store.getAt(i)
67864             });
67865             prevX = x;
67866             prevY = y;
67867         }
67868         
67869         if (path.length <= 1) {
67870             //nothing to be rendered
67871             return;    
67872         }
67873         
67874         if (me.smooth) {
67875             path = Ext.draw.Draw.smooth(path, 6);
67876         }
67877         
67878         //Correct path if we're animating timeAxis intervals
67879         if (chart.markerIndex && me.previousPath) {
67880             fromPath = me.previousPath;
67881             fromPath.splice(1, 2);
67882         } else {
67883             fromPath = path;
67884         }
67885
67886         // Only create a line if one doesn't exist.
67887         if (!me.line) {
67888             me.line = surface.add(Ext.apply({
67889                 type: 'path',
67890                 group: group,
67891                 path: dummyPath,
67892                 stroke: endLineStyle.stroke || endLineStyle.fill
67893             }, endLineStyle || {}));
67894             //unset fill here (there's always a default fill withing the themes).
67895             me.line.setAttributes({
67896                 fill: 'none'
67897             });
67898             if (!endLineStyle.stroke && colorArrayLength) {
67899                 me.line.setAttributes({
67900                     stroke: colorArrayStyle[seriesIdx % colorArrayLength]
67901                 }, true);
67902             }
67903             if (enableShadows) {
67904                 //create shadows
67905                 shadows = me.line.shadows = [];                
67906                 for (shindex = 0; shindex < lnsh; shindex++) {
67907                     shadowBarAttr = shadowAttributes[shindex];
67908                     shadowBarAttr = Ext.apply({}, shadowBarAttr, { path: dummyPath });
67909                     shadow = chart.surface.add(Ext.apply({}, {
67910                         type: 'path',
67911                         group: shadowGroups[shindex]
67912                     }, shadowBarAttr));
67913                     shadows.push(shadow);
67914                 }
67915             }
67916         }
67917         if (me.fill) {
67918             fillPath = path.concat([
67919                 ["L", x, bbox.y + bbox.height],
67920                 ["L", bbox.x, bbox.y + bbox.height],
67921                 ["L", bbox.x, firstY]
67922             ]);
67923             if (!me.fillPath) {
67924                 me.fillPath = surface.add({
67925                     group: group,
67926                     type: 'path',
67927                     opacity: endLineStyle.opacity || 0.3,
67928                     fill: colorArrayStyle[seriesIdx % colorArrayLength] || endLineStyle.fill,
67929                     path: dummyPath
67930                 });
67931             }
67932         }
67933         markerCount = showMarkers && markerGroup.getCount();
67934         if (chart.animate) {
67935             fill = me.fill;
67936             line = me.line;
67937             //Add renderer to line. There is not unique record associated with this.
67938             rendererAttributes = me.renderer(line, false, { path: path }, i, store);
67939             Ext.apply(rendererAttributes, endLineStyle || {}, {
67940                 stroke: endLineStyle.stroke || endLineStyle.fill
67941             });
67942             //fill should not be used here but when drawing the special fill path object
67943             delete rendererAttributes.fill;
67944             if (chart.markerIndex && me.previousPath) {
67945                 me.animation = animation = me.onAnimate(line, {
67946                     to: rendererAttributes,
67947                     from: {
67948                         path: fromPath
67949                     }
67950                 });
67951             } else {
67952                 me.animation = animation = me.onAnimate(line, {
67953                     to: rendererAttributes
67954                 });
67955             }
67956             //animate shadows
67957             if (enableShadows) {
67958                 shadows = line.shadows;
67959                 for(j = 0; j < lnsh; j++) {
67960                     if (chart.markerIndex && me.previousPath) {
67961                         me.onAnimate(shadows[j], {
67962                             to: { path: path },
67963                             from: { path: fromPath }
67964                         });
67965                     } else {
67966                         me.onAnimate(shadows[j], {
67967                             to: { path: path }
67968                         });
67969                     }
67970                 }
67971             }
67972             //animate fill path
67973             if (fill) {
67974                 me.onAnimate(me.fillPath, {
67975                     to: Ext.apply({}, {
67976                         path: fillPath,
67977                         fill: colorArrayStyle[seriesIdx % colorArrayLength] || endLineStyle.fill
67978                     }, endLineStyle || {})
67979                 });
67980             }
67981             //animate markers
67982             if (showMarkers) {
67983                 for(i = 0; i < ln; i++) {
67984                     item = markerGroup.getAt(i);
67985                     if (item) {
67986                         if (me.items[i]) {
67987                             rendererAttributes = me.renderer(item, store.getAt(i), item._to, i, store);
67988                             me.onAnimate(item, {
67989                                 to: Ext.apply(rendererAttributes, endMarkerStyle || {})
67990                             });
67991                         } else {
67992                             item.setAttributes(Ext.apply({
67993                                 hidden: true 
67994                             }, item._to), true);
67995                         }
67996                     }
67997                 }
67998                 for(; i < markerCount; i++) {
67999                     item = markerGroup.getAt(i);
68000                     item.hide(true);
68001                 }
68002 //                for(i = 0; i < (chart.markerIndex || 0)-1; i++) {
68003 //                    item = markerGroup.getAt(i);
68004 //                    item.hide(true);
68005 //                }
68006             }
68007         } else {
68008             rendererAttributes = me.renderer(me.line, false, { path: path, hidden: false }, i, store);
68009             Ext.apply(rendererAttributes, endLineStyle || {}, {
68010                 stroke: endLineStyle.stroke || endLineStyle.fill
68011             });
68012             //fill should not be used here but when drawing the special fill path object
68013             delete rendererAttributes.fill;
68014             me.line.setAttributes(rendererAttributes, true);
68015             //set path for shadows
68016             if (enableShadows) {
68017                 shadows = me.line.shadows;
68018                 for(j = 0; j < lnsh; j++) {
68019                     shadows[j].setAttributes({
68020                         path: path
68021                     }, true);
68022                 }
68023             }
68024             if (me.fill) {
68025                 me.fillPath.setAttributes({
68026                     path: fillPath
68027                 }, true);
68028             }
68029             if (showMarkers) {
68030                 for(i = 0; i < ln; i++) {
68031                     item = markerGroup.getAt(i);
68032                     if (item) {
68033                         if (me.items[i]) {
68034                             rendererAttributes = me.renderer(item, store.getAt(i), item._to, i, store);
68035                             item.setAttributes(Ext.apply(endMarkerStyle || {}, rendererAttributes || {}), true);
68036                         } else {
68037                             item.hide(true);
68038                         }
68039                     }
68040                 }
68041                 for(; i < markerCount; i++) {
68042                     item = markerGroup.getAt(i);
68043                     item.hide(true);
68044                 }
68045             }
68046         }
68047
68048         if (chart.markerIndex) {
68049             path.splice(1, 0, path[1], path[2]);
68050             me.previousPath = path;
68051         }
68052         me.renderLabels();
68053         me.renderCallouts();
68054     },
68055     
68056     // @private called when a label is to be created.
68057     onCreateLabel: function(storeItem, item, i, display) {
68058         var me = this,
68059             group = me.labelsGroup,
68060             config = me.label,
68061             bbox = me.bbox,
68062             endLabelStyle = Ext.apply(config, me.seriesLabelStyle);
68063
68064         return me.chart.surface.add(Ext.apply({
68065             'type': 'text',
68066             'text-anchor': 'middle',
68067             'group': group,
68068             'x': item.point[0],
68069             'y': bbox.y + bbox.height / 2
68070         }, endLabelStyle || {}));
68071     },
68072     
68073     // @private called when a label is to be created.
68074     onPlaceLabel: function(label, storeItem, item, i, display, animate) {
68075         var me = this,
68076             chart = me.chart,
68077             resizing = chart.resizing,
68078             config = me.label,
68079             format = config.renderer,
68080             field = config.field,
68081             bbox = me.bbox,
68082             x = item.point[0],
68083             y = item.point[1],
68084             radius = item.sprite.attr.radius,
68085             bb, width, height;
68086         
68087         label.setAttributes({
68088             text: format(storeItem.get(field)),
68089             hidden: true
68090         }, true);
68091         
68092         if (display == 'rotate') {
68093             label.setAttributes({
68094                 'text-anchor': 'start',
68095                 'rotation': {
68096                     x: x,
68097                     y: y,
68098                     degrees: -45
68099                 }
68100             }, true);
68101             //correct label position to fit into the box
68102             bb = label.getBBox();
68103             width = bb.width;
68104             height = bb.height;
68105             x = x < bbox.x? bbox.x : x;
68106             x = (x + width > bbox.x + bbox.width)? (x - (x + width - bbox.x - bbox.width)) : x;
68107             y = (y - height < bbox.y)? bbox.y + height : y;
68108         
68109         } else if (display == 'under' || display == 'over') {
68110             //TODO(nicolas): find out why width/height values in circle bounding boxes are undefined.
68111             bb = item.sprite.getBBox();
68112             bb.width = bb.width || (radius * 2);
68113             bb.height = bb.height || (radius * 2);
68114             y = y + (display == 'over'? -bb.height : bb.height);
68115             //correct label position to fit into the box
68116             bb = label.getBBox();
68117             width = bb.width/2;
68118             height = bb.height/2;
68119             x = x - width < bbox.x? bbox.x + width : x;
68120             x = (x + width > bbox.x + bbox.width) ? (x - (x + width - bbox.x - bbox.width)) : x;
68121             y = y - height < bbox.y? bbox.y + height : y;
68122             y = (y + height > bbox.y + bbox.height) ? (y - (y + height - bbox.y - bbox.height)) : y;
68123         }
68124         
68125         if (me.chart.animate && !me.chart.resizing) {
68126             label.show(true);
68127             me.onAnimate(label, {
68128                 to: {
68129                     x: x,
68130                     y: y
68131                 }
68132             });
68133         } else {
68134             label.setAttributes({
68135                 x: x,
68136                 y: y
68137             }, true);
68138             if (resizing) {
68139                 me.animation.on('afteranimate', function() {
68140                     label.show(true);
68141                 });
68142             } else {
68143                 label.show(true);
68144             }
68145         }
68146     },
68147
68148     //@private Overriding highlights.js highlightItem method.
68149     highlightItem: function() {
68150         var me = this;
68151         me.callParent(arguments);
68152         if (this.line && !this.highlighted) {
68153             if (!('__strokeWidth' in this.line)) {
68154                 this.line.__strokeWidth = this.line.attr['stroke-width'] || 0;
68155             }
68156             if (this.line.__anim) {
68157                 this.line.__anim.paused = true;
68158             }
68159             this.line.__anim = Ext.create('Ext.fx.Anim', {
68160                 target: this.line,
68161                 to: {
68162                     'stroke-width': this.line.__strokeWidth + 3
68163                 }
68164             });
68165             this.highlighted = true;
68166         }
68167     },
68168
68169     //@private Overriding highlights.js unHighlightItem method.
68170     unHighlightItem: function() {
68171         var me = this;
68172         me.callParent(arguments);
68173         if (this.line && this.highlighted) {
68174             this.line.__anim = Ext.create('Ext.fx.Anim', {
68175                 target: this.line,
68176                 to: {
68177                     'stroke-width': this.line.__strokeWidth
68178                 }
68179             });
68180             this.highlighted = false;
68181         }
68182     },
68183
68184     //@private called when a callout needs to be placed.
68185     onPlaceCallout : function(callout, storeItem, item, i, display, animate, index) {
68186         if (!display) {
68187             return;
68188         }
68189         
68190         var me = this,
68191             chart = me.chart,
68192             surface = chart.surface,
68193             resizing = chart.resizing,
68194             config = me.callouts,
68195             items = me.items,
68196             prev = i == 0? false : items[i -1].point,
68197             next = (i == items.length -1)? false : items[i +1].point,
68198             cur = [+item.point[0], +item.point[1]],
68199             dir, norm, normal, a, aprev, anext,
68200             offsetFromViz = config.offsetFromViz || 30,
68201             offsetToSide = config.offsetToSide || 10,
68202             offsetBox = config.offsetBox || 3,
68203             boxx, boxy, boxw, boxh,
68204             p, clipRect = me.clipRect,
68205             bbox = {
68206                 width: config.styles.width || 10,
68207                 height: config.styles.height || 10
68208             },
68209             x, y;
68210
68211         //get the right two points
68212         if (!prev) {
68213             prev = cur;
68214         }
68215         if (!next) {
68216             next = cur;
68217         }
68218         a = (next[1] - prev[1]) / (next[0] - prev[0]);
68219         aprev = (cur[1] - prev[1]) / (cur[0] - prev[0]);
68220         anext = (next[1] - cur[1]) / (next[0] - cur[0]);
68221         
68222         norm = Math.sqrt(1 + a * a);
68223         dir = [1 / norm, a / norm];
68224         normal = [-dir[1], dir[0]];
68225         
68226         //keep the label always on the outer part of the "elbow"
68227         if (aprev > 0 && anext < 0 && normal[1] < 0
68228             || aprev < 0 && anext > 0 && normal[1] > 0) {
68229             normal[0] *= -1;
68230             normal[1] *= -1;
68231         } else if (Math.abs(aprev) < Math.abs(anext) && normal[0] < 0
68232                    || Math.abs(aprev) > Math.abs(anext) && normal[0] > 0) {
68233             normal[0] *= -1;
68234             normal[1] *= -1;
68235         }
68236         //position
68237         x = cur[0] + normal[0] * offsetFromViz;
68238         y = cur[1] + normal[1] * offsetFromViz;
68239
68240         //box position and dimensions
68241         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
68242         boxy = y - bbox.height /2 - offsetBox;
68243         boxw = bbox.width + 2 * offsetBox;
68244         boxh = bbox.height + 2 * offsetBox;
68245         
68246         //now check if we're out of bounds and invert the normal vector correspondingly
68247         //this may add new overlaps between labels (but labels won't be out of bounds).
68248         if (boxx < clipRect[0] || (boxx + boxw) > (clipRect[0] + clipRect[2])) {
68249             normal[0] *= -1;
68250         }
68251         if (boxy < clipRect[1] || (boxy + boxh) > (clipRect[1] + clipRect[3])) {
68252             normal[1] *= -1;
68253         }
68254
68255         //update positions
68256         x = cur[0] + normal[0] * offsetFromViz;
68257         y = cur[1] + normal[1] * offsetFromViz;
68258         
68259         //update box position and dimensions
68260         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
68261         boxy = y - bbox.height /2 - offsetBox;
68262         boxw = bbox.width + 2 * offsetBox;
68263         boxh = bbox.height + 2 * offsetBox;
68264         
68265         if (chart.animate) {
68266             //set the line from the middle of the pie to the box.
68267             me.onAnimate(callout.lines, {
68268                 to: {
68269                     path: ["M", cur[0], cur[1], "L", x, y, "Z"]
68270                 }
68271             });
68272             //set component position
68273             if (callout.panel) {
68274                 callout.panel.setPosition(boxx, boxy, true);
68275             }
68276         }
68277         else {
68278             //set the line from the middle of the pie to the box.
68279             callout.lines.setAttributes({
68280                 path: ["M", cur[0], cur[1], "L", x, y, "Z"]
68281             }, true);
68282             //set component position
68283             if (callout.panel) {
68284                 callout.panel.setPosition(boxx, boxy);
68285             }
68286         }
68287         for (p in callout) {
68288             callout[p].show(true);
68289         }
68290     },
68291     
68292     isItemInPoint: function(x, y, item, i) {
68293         var me = this,
68294             items = me.items,
68295             tolerance = me.selectionTolerance,
68296             result = null,
68297             prevItem,
68298             nextItem,
68299             prevPoint,
68300             nextPoint,
68301             ln,
68302             x1,
68303             y1,
68304             x2,
68305             y2,
68306             xIntersect,
68307             yIntersect,
68308             dist1, dist2, dist, midx, midy,
68309             sqrt = Math.sqrt, abs = Math.abs;
68310         
68311         nextItem = items[i];
68312         prevItem = i && items[i - 1];
68313         
68314         if (i >= ln) {
68315             prevItem = items[ln - 1];
68316         }
68317         prevPoint = prevItem && prevItem.point;
68318         nextPoint = nextItem && nextItem.point;
68319         x1 = prevItem ? prevPoint[0] : nextPoint[0] - tolerance;
68320         y1 = prevItem ? prevPoint[1] : nextPoint[1];
68321         x2 = nextItem ? nextPoint[0] : prevPoint[0] + tolerance;
68322         y2 = nextItem ? nextPoint[1] : prevPoint[1];
68323         dist1 = sqrt((x - x1) * (x - x1) + (y - y1) * (y - y1));
68324         dist2 = sqrt((x - x2) * (x - x2) + (y - y2) * (y - y2));
68325         dist = Math.min(dist1, dist2);
68326         
68327         if (dist <= tolerance) {
68328             return dist == dist1? prevItem : nextItem;
68329         }
68330         return false;
68331     },
68332     
68333     // @private toggle visibility of all series elements (markers, sprites).
68334     toggleAll: function(show) {
68335         var me = this,
68336             i, ln, shadow, shadows;
68337         if (!show) {
68338             Ext.chart.series.Line.superclass.hideAll.call(me);
68339         }
68340         else {
68341             Ext.chart.series.Line.superclass.showAll.call(me);
68342         }
68343         if (me.line) {
68344             me.line.setAttributes({
68345                 hidden: !show
68346             }, true);
68347             //hide shadows too
68348             if (me.line.shadows) {
68349                 for (i = 0, shadows = me.line.shadows, ln = shadows.length; i < ln; i++) {
68350                     shadow = shadows[i];
68351                     shadow.setAttributes({
68352                         hidden: !show
68353                     }, true);
68354                 }
68355             }
68356         }
68357         if (me.fillPath) {
68358             me.fillPath.setAttributes({
68359                 hidden: !show
68360             }, true);
68361         }
68362     },
68363     
68364     // @private hide all series elements (markers, sprites).
68365     hideAll: function() {
68366         this.toggleAll(false);
68367     },
68368     
68369     // @private hide all series elements (markers, sprites).
68370     showAll: function() {
68371         this.toggleAll(true);
68372     }
68373 });
68374 /**
68375  * @class Ext.chart.series.Pie
68376  * @extends Ext.chart.series.Series
68377  * 
68378  * Creates a Pie Chart. A Pie Chart is a useful visualization technique to display quantitative information for different 
68379  * categories that also have a meaning as a whole.
68380  * As with all other series, the Pie Series must be appended in the *series* Chart array configuration. See the Chart 
68381  * documentation for more information. A typical configuration object for the pie series could be:
68382  * 
68383 {@img Ext.chart.series.Pie/Ext.chart.series.Pie.png Ext.chart.series.Pie chart series}
68384   <pre><code>
68385     var store = Ext.create('Ext.data.JsonStore', {
68386         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
68387         data: [
68388             {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
68389             {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
68390             {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
68391             {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
68392             {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}                                                
68393         ]
68394     });
68395     
68396     Ext.create('Ext.chart.Chart', {
68397         renderTo: Ext.getBody(),
68398         width: 500,
68399         height: 300,
68400         animate: true,
68401         store: store,
68402         theme: 'Base:gradients',
68403         series: [{
68404             type: 'pie',
68405             field: 'data1',
68406             showInLegend: true,
68407             tips: {
68408               trackMouse: true,
68409               width: 140,
68410               height: 28,
68411               renderer: function(storeItem, item) {
68412                 //calculate and display percentage on hover
68413                 var total = 0;
68414                 store.each(function(rec) {
68415                     total += rec.get('data1');
68416                 });
68417                 this.setTitle(storeItem.get('name') + ': ' + Math.round(storeItem.get('data1') / total * 100) + '%');
68418               }
68419             },
68420             highlight: {
68421               segment: {
68422                 margin: 20
68423               }
68424             },
68425             label: {
68426                 field: 'name',
68427                 display: 'rotate',
68428                 contrast: true,
68429                 font: '18px Arial'
68430             }
68431         }]    
68432     });
68433    </code></pre>
68434  
68435  * 
68436  * In this configuration we set `pie` as the type for the series, set an object with specific style properties for highlighting options 
68437  * (triggered when hovering elements). We also set true to `showInLegend` so all the pie slices can be represented by a legend item. 
68438  * 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 
68439  * 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. 
68440  * 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 
68441  * and size through the `font` parameter. 
68442  * 
68443  * @xtype pie
68444  *
68445  */
68446 Ext.define('Ext.chart.series.Pie', {
68447
68448     /* Begin Definitions */
68449
68450     alternateClassName: ['Ext.chart.PieSeries', 'Ext.chart.PieChart'],
68451
68452     extend: 'Ext.chart.series.Series',
68453
68454     /* End Definitions */
68455
68456     type: "pie",
68457     
68458     alias: 'series.pie',
68459
68460     rad: Math.PI / 180,
68461
68462     /**
68463      * @cfg {Number} highlightDuration
68464      * The duration for the pie slice highlight effect.
68465      */
68466     highlightDuration: 150,
68467
68468     /**
68469      * @cfg {String} angleField
68470      * The store record field name to be used for the pie angles.
68471      * The values bound to this field name must be positive real numbers.
68472      * This parameter is required.
68473      */
68474     angleField: false,
68475
68476     /**
68477      * @cfg {String} lengthField
68478      * The store record field name to be used for the pie slice lengths.
68479      * The values bound to this field name must be positive real numbers.
68480      * This parameter is optional.
68481      */
68482     lengthField: false,
68483
68484     /**
68485      * @cfg {Boolean|Number} donut
68486      * Whether to set the pie chart as donut chart.
68487      * Default's false. Can be set to a particular percentage to set the radius
68488      * of the donut chart.
68489      */
68490     donut: false,
68491
68492     /**
68493      * @cfg {Boolean} showInLegend
68494      * Whether to add the pie chart elements as legend items. Default's false.
68495      */
68496     showInLegend: false,
68497
68498     /**
68499      * @cfg {Array} colorSet
68500      * An array of color values which will be used, in order, as the pie slice fill colors.
68501      */
68502     
68503     /**
68504      * @cfg {Object} style
68505      * An object containing styles for overriding series styles from Theming.
68506      */
68507     style: {},
68508     
68509     constructor: function(config) {
68510         this.callParent(arguments);
68511         var me = this,
68512             chart = me.chart,
68513             surface = chart.surface,
68514             store = chart.store,
68515             shadow = chart.shadow, i, l, cfg;
68516         Ext.applyIf(me, {
68517             highlightCfg: {
68518                 segment: {
68519                     margin: 20
68520                 }
68521             }
68522         });
68523         Ext.apply(me, config, {            
68524             shadowAttributes: [{
68525                 "stroke-width": 6,
68526                 "stroke-opacity": 1,
68527                 stroke: 'rgb(200, 200, 200)',
68528                 translate: {
68529                     x: 1.2,
68530                     y: 2
68531                 }
68532             },
68533             {
68534                 "stroke-width": 4,
68535                 "stroke-opacity": 1,
68536                 stroke: 'rgb(150, 150, 150)',
68537                 translate: {
68538                     x: 0.9,
68539                     y: 1.5
68540                 }
68541             },
68542             {
68543                 "stroke-width": 2,
68544                 "stroke-opacity": 1,
68545                 stroke: 'rgb(100, 100, 100)',
68546                 translate: {
68547                     x: 0.6,
68548                     y: 1
68549                 }
68550             }]
68551         });
68552         me.group = surface.getGroup(me.seriesId);
68553         if (shadow) {
68554             for (i = 0, l = me.shadowAttributes.length; i < l; i++) {
68555                 me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
68556             }
68557         }
68558         surface.customAttributes.segment = function(opt) {
68559             return me.getSegment(opt);
68560         };
68561     },
68562     
68563     //@private updates some onbefore render parameters.
68564     initialize: function() {
68565         var me = this,
68566             store = me.chart.substore || me.chart.store;
68567         //Add yFields to be used in Legend.js
68568         me.yField = [];
68569         if (me.label.field) {
68570             store.each(function(rec) {
68571                 me.yField.push(rec.get(me.label.field));
68572             });
68573         }
68574     },
68575
68576     // @private returns an object with properties for a PieSlice.
68577     getSegment: function(opt) {
68578         var me = this,
68579             rad = me.rad,
68580             cos = Math.cos,
68581             sin = Math.sin,
68582             abs = Math.abs,
68583             x = me.centerX,
68584             y = me.centerY,
68585             x1 = 0, x2 = 0, x3 = 0, x4 = 0,
68586             y1 = 0, y2 = 0, y3 = 0, y4 = 0,
68587             delta = 1e-2,
68588             r = opt.endRho - opt.startRho,
68589             startAngle = opt.startAngle,
68590             endAngle = opt.endAngle,
68591             midAngle = (startAngle + endAngle) / 2 * rad,
68592             margin = opt.margin || 0,
68593             flag = abs(endAngle - startAngle) > 180,
68594             a1 = Math.min(startAngle, endAngle) * rad,
68595             a2 = Math.max(startAngle, endAngle) * rad,
68596             singleSlice = false;
68597
68598         x += margin * cos(midAngle);
68599         y += margin * sin(midAngle);
68600
68601         x1 = x + opt.startRho * cos(a1);
68602         y1 = y + opt.startRho * sin(a1);
68603
68604         x2 = x + opt.endRho * cos(a1);
68605         y2 = y + opt.endRho * sin(a1);
68606
68607         x3 = x + opt.startRho * cos(a2);
68608         y3 = y + opt.startRho * sin(a2);
68609
68610         x4 = x + opt.endRho * cos(a2);
68611         y4 = y + opt.endRho * sin(a2);
68612
68613         if (abs(x1 - x3) <= delta && abs(y1 - y3) <= delta) {
68614             singleSlice = true;
68615         }
68616         //Solves mysterious clipping bug with IE
68617         if (singleSlice) {
68618             return {
68619                 path: [
68620                 ["M", x1, y1],
68621                 ["L", x2, y2],
68622                 ["A", opt.endRho, opt.endRho, 0, +flag, 1, x4, y4],
68623                 ["Z"]]
68624             };
68625         } else {
68626             return {
68627                 path: [
68628                 ["M", x1, y1],
68629                 ["L", x2, y2],
68630                 ["A", opt.endRho, opt.endRho, 0, +flag, 1, x4, y4],
68631                 ["L", x3, y3],
68632                 ["A", opt.startRho, opt.startRho, 0, +flag, 0, x1, y1],
68633                 ["Z"]]
68634             };
68635         }
68636     },
68637
68638     // @private utility function to calculate the middle point of a pie slice.
68639     calcMiddle: function(item) {
68640         var me = this,
68641             rad = me.rad,
68642             slice = item.slice,
68643             x = me.centerX,
68644             y = me.centerY,
68645             startAngle = slice.startAngle,
68646             endAngle = slice.endAngle,
68647             donut = +me.donut,
68648             a1 = Math.min(startAngle, endAngle) * rad,
68649             a2 = Math.max(startAngle, endAngle) * rad,
68650             midAngle = -(a1 + (a2 - a1) / 2),
68651             xm = x + (item.endRho + item.startRho) / 2 * Math.cos(midAngle),
68652             ym = y - (item.endRho + item.startRho) / 2 * Math.sin(midAngle);
68653
68654         item.middle = {
68655             x: xm,
68656             y: ym
68657         };
68658     },
68659
68660     /**
68661      * Draws the series for the current chart.
68662      */
68663     drawSeries: function() {
68664         var me = this,
68665             store = me.chart.substore || me.chart.store,
68666             group = me.group,
68667             animate = me.chart.animate,
68668             field = me.angleField || me.field || me.xField,
68669             lenField = [].concat(me.lengthField),
68670             totalLenField = 0,
68671             colors = me.colorSet,
68672             chart = me.chart,
68673             surface = chart.surface,
68674             chartBBox = chart.chartBBox,
68675             enableShadows = chart.shadow,
68676             shadowGroups = me.shadowGroups,
68677             shadowAttributes = me.shadowAttributes,
68678             lnsh = shadowGroups.length,
68679             rad = me.rad,
68680             layers = lenField.length,
68681             rhoAcum = 0,
68682             donut = +me.donut,
68683             layerTotals = [],
68684             values = {},
68685             fieldLength,
68686             items = [],
68687             passed = false,
68688             totalField = 0,
68689             maxLenField = 0,
68690             cut = 9,
68691             defcut = true,
68692             angle = 0,
68693             seriesStyle = me.seriesStyle,
68694             seriesLabelStyle = me.seriesLabelStyle,
68695             colorArrayStyle = me.colorArrayStyle,
68696             colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
68697             gutterX = chart.maxGutter[0],
68698             gutterY = chart.maxGutter[1],
68699             rendererAttributes,
68700             shadowGroup,
68701             shadowAttr,
68702             shadows,
68703             shadow,
68704             shindex,
68705             centerX,
68706             centerY,
68707             deltaRho,
68708             first = 0,
68709             slice,
68710             slices,
68711             sprite,
68712             value,
68713             item,
68714             lenValue,
68715             ln,
68716             record,
68717             i,
68718             j,
68719             startAngle,
68720             endAngle,
68721             middleAngle,
68722             sliceLength,
68723             path,
68724             p,
68725             spriteOptions, bbox;
68726         
68727         Ext.apply(seriesStyle, me.style || {});
68728
68729         me.setBBox();
68730         bbox = me.bbox;
68731
68732         //override theme colors
68733         if (me.colorSet) {
68734             colorArrayStyle = me.colorSet;
68735             colorArrayLength = colorArrayStyle.length;
68736         }
68737         
68738         //if not store or store is empty then there's nothing to draw
68739         if (!store || !store.getCount()) {
68740             return;
68741         }
68742         
68743         me.unHighlightItem();
68744         me.cleanHighlights();
68745
68746         centerX = me.centerX = chartBBox.x + (chartBBox.width / 2);
68747         centerY = me.centerY = chartBBox.y + (chartBBox.height / 2);
68748         me.radius = Math.min(centerX - chartBBox.x, centerY - chartBBox.y);
68749         me.slices = slices = [];
68750         me.items = items = [];
68751
68752         store.each(function(record, i) {
68753             if (this.__excludes && this.__excludes[i]) {
68754                 //hidden series
68755                 return;
68756             }
68757             totalField += +record.get(field);
68758             if (lenField[0]) {
68759                 for (j = 0, totalLenField = 0; j < layers; j++) {
68760                     totalLenField += +record.get(lenField[j]);
68761                 }
68762                 layerTotals[i] = totalLenField;
68763                 maxLenField = Math.max(maxLenField, totalLenField);
68764             }
68765         }, this);
68766
68767         store.each(function(record, i) {
68768             if (this.__excludes && this.__excludes[i]) {
68769                 //hidden series
68770                 return;
68771             } 
68772             value = record.get(field);
68773             middleAngle = angle - 360 * value / totalField / 2;
68774             // TODO - Put up an empty circle
68775             if (isNaN(middleAngle)) {
68776                 middleAngle = 360;
68777                 value = 1;
68778                 totalField = 1;
68779             }
68780             // First slice
68781             if (!i || first == 0) {
68782                 angle = 360 - middleAngle;
68783                 me.firstAngle = angle;
68784                 middleAngle = angle - 360 * value / totalField / 2;
68785             }
68786             endAngle = angle - 360 * value / totalField;
68787             slice = {
68788                 series: me,
68789                 value: value,
68790                 startAngle: angle,
68791                 endAngle: endAngle,
68792                 storeItem: record
68793             };
68794             if (lenField[0]) {
68795                 lenValue = layerTotals[i];
68796                 slice.rho = me.radius * (lenValue / maxLenField);
68797             } else {
68798                 slice.rho = me.radius;
68799             }
68800             slices[i] = slice;
68801             if((slice.startAngle % 360) == (slice.endAngle % 360)) {
68802                 slice.startAngle -= 0.0001;
68803             }
68804             angle = endAngle;
68805             first++;
68806         }, me);
68807         
68808         //do all shadows first.
68809         if (enableShadows) {
68810             for (i = 0, ln = slices.length; i < ln; i++) {
68811                 if (this.__excludes && this.__excludes[i]) {
68812                     //hidden series
68813                     continue;
68814                 }
68815                 slice = slices[i];
68816                 slice.shadowAttrs = [];
68817                 for (j = 0, rhoAcum = 0, shadows = []; j < layers; j++) {
68818                     sprite = group.getAt(i * layers + j);
68819                     deltaRho = lenField[j] ? store.getAt(i).get(lenField[j]) / layerTotals[i] * slice.rho: slice.rho;
68820                     //set pie slice properties
68821                     rendererAttributes = {
68822                         segment: {
68823                             startAngle: slice.startAngle,
68824                             endAngle: slice.endAngle,
68825                             margin: 0,
68826                             rho: slice.rho,
68827                             startRho: rhoAcum + (deltaRho * donut / 100),
68828                             endRho: rhoAcum + deltaRho
68829                         }
68830                     };
68831                     //create shadows
68832                     for (shindex = 0, shadows = []; shindex < lnsh; shindex++) {
68833                         shadowAttr = shadowAttributes[shindex];
68834                         shadow = shadowGroups[shindex].getAt(i);
68835                         if (!shadow) {
68836                             shadow = chart.surface.add(Ext.apply({},
68837                             {
68838                                 type: 'path',
68839                                 group: shadowGroups[shindex],
68840                                 strokeLinejoin: "round"
68841                             },
68842                             rendererAttributes, shadowAttr));
68843                         }
68844                         if (animate) {
68845                             rendererAttributes = me.renderer(shadow, store.getAt(i), Ext.apply({},
68846                             rendererAttributes, shadowAttr), i, store);
68847                             me.onAnimate(shadow, {
68848                                 to: rendererAttributes
68849                             });
68850                         } else {
68851                             rendererAttributes = me.renderer(shadow, store.getAt(i), Ext.apply(shadowAttr, {
68852                                 hidden: false
68853                             }), i, store);
68854                             shadow.setAttributes(rendererAttributes, true);
68855                         }
68856                         shadows.push(shadow);
68857                     }
68858                     slice.shadowAttrs[j] = shadows;
68859                 }
68860             }
68861         }
68862         //do pie slices after.
68863         for (i = 0, ln = slices.length; i < ln; i++) {
68864             if (this.__excludes && this.__excludes[i]) {
68865                 //hidden series
68866                 continue;
68867             }
68868             slice = slices[i];
68869             for (j = 0, rhoAcum = 0; j < layers; j++) {
68870                 sprite = group.getAt(i * layers + j);
68871                 deltaRho = lenField[j] ? store.getAt(i).get(lenField[j]) / layerTotals[i] * slice.rho: slice.rho;
68872                 //set pie slice properties
68873                 rendererAttributes = Ext.apply({
68874                     segment: {
68875                         startAngle: slice.startAngle,
68876                         endAngle: slice.endAngle,
68877                         margin: 0,
68878                         rho: slice.rho,
68879                         startRho: rhoAcum + (deltaRho * donut / 100),
68880                         endRho: rhoAcum + deltaRho
68881                     } 
68882                 }, Ext.apply(seriesStyle, colorArrayStyle && { fill: colorArrayStyle[(layers > 1? j : i) % colorArrayLength] } || {}));
68883                 item = Ext.apply({},
68884                 rendererAttributes.segment, {
68885                     slice: slice,
68886                     series: me,
68887                     storeItem: slice.storeItem,
68888                     index: i
68889                 });
68890                 me.calcMiddle(item);
68891                 if (enableShadows) {
68892                     item.shadows = slice.shadowAttrs[j];
68893                 }
68894                 items[i] = item;
68895                 // Create a new sprite if needed (no height)
68896                 if (!sprite) {
68897                     spriteOptions = Ext.apply({
68898                         type: "path",
68899                         group: group,
68900                         middle: item.middle
68901                     }, Ext.apply(seriesStyle, colorArrayStyle && { fill: colorArrayStyle[(layers > 1? j : i) % colorArrayLength] } || {}));
68902                     sprite = surface.add(Ext.apply(spriteOptions, rendererAttributes));
68903                 }
68904                 slice.sprite = slice.sprite || [];
68905                 item.sprite = sprite;
68906                 slice.sprite.push(sprite);
68907                 slice.point = [item.middle.x, item.middle.y];
68908                 if (animate) {
68909                     rendererAttributes = me.renderer(sprite, store.getAt(i), rendererAttributes, i, store);
68910                     sprite._to = rendererAttributes;
68911                     sprite._animating = true;
68912                     me.onAnimate(sprite, {
68913                         to: rendererAttributes,
68914                         listeners: {
68915                             afteranimate: {
68916                                 fn: function() {
68917                                     this._animating = false;
68918                                 },
68919                                 scope: sprite
68920                             }
68921                         }
68922                     });
68923                 } else {
68924                     rendererAttributes = me.renderer(sprite, store.getAt(i), Ext.apply(rendererAttributes, {
68925                         hidden: false
68926                     }), i, store);
68927                     sprite.setAttributes(rendererAttributes, true);
68928                 }
68929                 rhoAcum += deltaRho;
68930             }
68931         }
68932         
68933         // Hide unused bars
68934         ln = group.getCount();
68935         for (i = 0; i < ln; i++) {
68936             if (!slices[(i / layers) >> 0] && group.getAt(i)) {
68937                 group.getAt(i).hide(true);
68938             }
68939         }
68940         if (enableShadows) {
68941             lnsh = shadowGroups.length;
68942             for (shindex = 0; shindex < ln; shindex++) {
68943                 if (!slices[(shindex / layers) >> 0]) {
68944                     for (j = 0; j < lnsh; j++) {
68945                         if (shadowGroups[j].getAt(shindex)) {
68946                             shadowGroups[j].getAt(shindex).hide(true);
68947                         }
68948                     }
68949                 }
68950             }
68951         }
68952         me.renderLabels();
68953         me.renderCallouts();
68954     },
68955
68956     // @private callback for when creating a label sprite.
68957     onCreateLabel: function(storeItem, item, i, display) {
68958         var me = this,
68959             group = me.labelsGroup,
68960             config = me.label,
68961             centerX = me.centerX,
68962             centerY = me.centerY,
68963             middle = item.middle,
68964             endLabelStyle = Ext.apply(me.seriesLabelStyle || {}, config || {});
68965         
68966         return me.chart.surface.add(Ext.apply({
68967             'type': 'text',
68968             'text-anchor': 'middle',
68969             'group': group,
68970             'x': middle.x,
68971             'y': middle.y
68972         }, endLabelStyle));
68973     },
68974
68975     // @private callback for when placing a label sprite.
68976     onPlaceLabel: function(label, storeItem, item, i, display, animate, index) {
68977         var me = this,
68978             chart = me.chart,
68979             resizing = chart.resizing,
68980             config = me.label,
68981             format = config.renderer,
68982             field = [].concat(config.field),
68983             centerX = me.centerX,
68984             centerY = me.centerY,
68985             middle = item.middle,
68986             opt = {
68987                 x: middle.x,
68988                 y: middle.y
68989             },
68990             x = middle.x - centerX,
68991             y = middle.y - centerY,
68992             from = {},
68993             rho = 1,
68994             theta = Math.atan2(y, x || 1),
68995             dg = theta * 180 / Math.PI,
68996             prevDg;
68997         
68998         function fixAngle(a) {
68999             if (a < 0) a += 360;
69000             return a % 360;
69001         }
69002
69003         label.setAttributes({
69004             text: format(storeItem.get(field[index]))
69005         }, true);
69006
69007         switch (display) {
69008         case 'outside':
69009             rho = Math.sqrt(x * x + y * y) * 2;
69010             //update positions
69011             opt.x = rho * Math.cos(theta) + centerX;
69012             opt.y = rho * Math.sin(theta) + centerY;
69013             break;
69014
69015         case 'rotate':
69016             dg = fixAngle(dg);
69017             dg = (dg > 90 && dg < 270) ? dg + 180: dg;
69018
69019             prevDg = label.attr.rotation.degrees;
69020             if (prevDg != null && Math.abs(prevDg - dg) > 180) {
69021                 if (dg > prevDg) {
69022                     dg -= 360;
69023                 } else {
69024                     dg += 360;
69025                 }
69026                 dg = dg % 360;
69027             } else {
69028                 dg = fixAngle(dg);
69029             }
69030             //update rotation angle
69031             opt.rotate = {
69032                 degrees: dg,
69033                 x: opt.x,
69034                 y: opt.y
69035             };
69036             break;
69037
69038         default:
69039             break;
69040         }
69041         //ensure the object has zero translation
69042         opt.translate = {
69043             x: 0, y: 0    
69044         };
69045         if (animate && !resizing && (display != 'rotate' || prevDg != null)) {
69046             me.onAnimate(label, {
69047                 to: opt
69048             });
69049         } else {
69050             label.setAttributes(opt, true);
69051         }
69052         label._from = from;
69053     },
69054
69055     // @private callback for when placing a callout sprite.
69056     onPlaceCallout: function(callout, storeItem, item, i, display, animate, index) {
69057         var me = this,
69058             chart = me.chart,
69059             resizing = chart.resizing,
69060             config = me.callouts,
69061             centerX = me.centerX,
69062             centerY = me.centerY,
69063             middle = item.middle,
69064             opt = {
69065                 x: middle.x,
69066                 y: middle.y
69067             },
69068             x = middle.x - centerX,
69069             y = middle.y - centerY,
69070             rho = 1,
69071             rhoCenter,
69072             theta = Math.atan2(y, x || 1),
69073             bbox = callout.label.getBBox(),
69074             offsetFromViz = 20,
69075             offsetToSide = 10,
69076             offsetBox = 10,
69077             p;
69078
69079         //should be able to config this.
69080         rho = item.endRho + offsetFromViz;
69081         rhoCenter = (item.endRho + item.startRho) / 2 + (item.endRho - item.startRho) / 3;
69082         //update positions
69083         opt.x = rho * Math.cos(theta) + centerX;
69084         opt.y = rho * Math.sin(theta) + centerY;
69085
69086         x = rhoCenter * Math.cos(theta);
69087         y = rhoCenter * Math.sin(theta);
69088
69089         if (chart.animate) {
69090             //set the line from the middle of the pie to the box.
69091             me.onAnimate(callout.lines, {
69092                 to: {
69093                     path: ["M", x + centerX, y + centerY, "L", opt.x, opt.y, "Z", "M", opt.x, opt.y, "l", x > 0 ? offsetToSide: -offsetToSide, 0, "z"]
69094                 }
69095             });
69096             //set box position
69097             me.onAnimate(callout.box, {
69098                 to: {
69099                     x: opt.x + (x > 0 ? offsetToSide: -(offsetToSide + bbox.width + 2 * offsetBox)),
69100                     y: opt.y + (y > 0 ? ( - bbox.height - offsetBox / 2) : ( - bbox.height - offsetBox / 2)),
69101                     width: bbox.width + 2 * offsetBox,
69102                     height: bbox.height + 2 * offsetBox
69103                 }
69104             });
69105             //set text position
69106             me.onAnimate(callout.label, {
69107                 to: {
69108                     x: opt.x + (x > 0 ? (offsetToSide + offsetBox) : -(offsetToSide + bbox.width + offsetBox)),
69109                     y: opt.y + (y > 0 ? -bbox.height / 4: -bbox.height / 4)
69110                 }
69111             });
69112         } else {
69113             //set the line from the middle of the pie to the box.
69114             callout.lines.setAttributes({
69115                 path: ["M", x + centerX, y + centerY, "L", opt.x, opt.y, "Z", "M", opt.x, opt.y, "l", x > 0 ? offsetToSide: -offsetToSide, 0, "z"]
69116             },
69117             true);
69118             //set box position
69119             callout.box.setAttributes({
69120                 x: opt.x + (x > 0 ? offsetToSide: -(offsetToSide + bbox.width + 2 * offsetBox)),
69121                 y: opt.y + (y > 0 ? ( - bbox.height - offsetBox / 2) : ( - bbox.height - offsetBox / 2)),
69122                 width: bbox.width + 2 * offsetBox,
69123                 height: bbox.height + 2 * offsetBox
69124             },
69125             true);
69126             //set text position
69127             callout.label.setAttributes({
69128                 x: opt.x + (x > 0 ? (offsetToSide + offsetBox) : -(offsetToSide + bbox.width + offsetBox)),
69129                 y: opt.y + (y > 0 ? -bbox.height / 4: -bbox.height / 4)
69130             },
69131             true);
69132         }
69133         for (p in callout) {
69134             callout[p].show(true);
69135         }
69136     },
69137
69138     // @private handles sprite animation for the series.
69139     onAnimate: function(sprite, attr) {
69140         sprite.show();
69141         return this.callParent(arguments);
69142     },
69143
69144     isItemInPoint: function(x, y, item, i) {
69145         var me = this,
69146             cx = me.centerX,
69147             cy = me.centerY,
69148             abs = Math.abs,
69149             dx = abs(x - cx),
69150             dy = abs(y - cy),
69151             startAngle = item.startAngle,
69152             endAngle = item.endAngle,
69153             rho = Math.sqrt(dx * dx + dy * dy),
69154             angle = Math.atan2(y - cy, x - cx) / me.rad + 360;
69155         
69156         // normalize to the same range of angles created by drawSeries
69157         if (angle > me.firstAngle) {
69158             angle -= 360;
69159         }
69160         return (angle <= startAngle && angle > endAngle
69161                 && rho >= item.startRho && rho <= item.endRho);
69162     },
69163     
69164     // @private hides all elements in the series.
69165     hideAll: function() {
69166         var i, l, shadow, shadows, sh, lsh, sprite;
69167         if (!isNaN(this._index)) {
69168             this.__excludes = this.__excludes || [];
69169             this.__excludes[this._index] = true;
69170             sprite = this.slices[this._index].sprite;
69171             for (sh = 0, lsh = sprite.length; sh < lsh; sh++) {
69172                 sprite[sh].setAttributes({
69173                     hidden: true
69174                 }, true);
69175             }
69176             if (this.slices[this._index].shadowAttrs) {
69177                 for (i = 0, shadows = this.slices[this._index].shadowAttrs, l = shadows.length; i < l; i++) {
69178                     shadow = shadows[i];
69179                     for (sh = 0, lsh = shadow.length; sh < lsh; sh++) {
69180                         shadow[sh].setAttributes({
69181                             hidden: true
69182                         }, true);
69183                     }
69184                 }
69185             }
69186             this.drawSeries();
69187         }
69188     },
69189     
69190     // @private shows all elements in the series.
69191     showAll: function() {
69192         if (!isNaN(this._index)) {
69193             this.__excludes[this._index] = false;
69194             this.drawSeries();
69195         }
69196     },
69197
69198     /**
69199      * Highlight the specified item. If no item is provided the whole series will be highlighted.
69200      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
69201      */
69202     highlightItem: function(item) {
69203         var me = this,
69204             rad = me.rad;
69205         item = item || this.items[this._index];
69206         
69207         //TODO(nico): sometimes in IE itemmouseover is triggered
69208         //twice without triggering itemmouseout in between. This
69209         //fixes the highlighting bug. Eventually, events should be
69210         //changed to trigger one itemmouseout between two itemmouseovers.
69211         this.unHighlightItem();
69212         
69213         if (!item || item.sprite && item.sprite._animating) {
69214             return;
69215         }
69216         me.callParent([item]);
69217         if (!me.highlight) {
69218             return;
69219         }
69220         if ('segment' in me.highlightCfg) {
69221             var highlightSegment = me.highlightCfg.segment,
69222                 animate = me.chart.animate,
69223                 attrs, i, shadows, shadow, ln, to, itemHighlightSegment, prop;
69224             //animate labels
69225             if (me.labelsGroup) {
69226                 var group = me.labelsGroup,
69227                     display = me.label.display,
69228                     label = group.getAt(item.index),
69229                     middle = (item.startAngle + item.endAngle) / 2 * rad,
69230                     r = highlightSegment.margin || 0,
69231                     x = r * Math.cos(middle),
69232                     y = r * Math.sin(middle);
69233
69234                 //TODO(nico): rounding to 1e-10
69235                 //gives the right translation. Translation
69236                 //was buggy for very small numbers. In this
69237                 //case we're not looking to translate to very small
69238                 //numbers but not to translate at all.
69239                 if (Math.abs(x) < 1e-10) {
69240                     x = 0;
69241                 }
69242                 if (Math.abs(y) < 1e-10) {
69243                     y = 0;
69244                 }
69245                 
69246                 if (animate) {
69247                     label.stopAnimation();
69248                     label.animate({
69249                         to: {
69250                             translate: {
69251                                 x: x,
69252                                 y: y
69253                             }
69254                         },
69255                         duration: me.highlightDuration
69256                     });
69257                 }
69258                 else {
69259                     label.setAttributes({
69260                         translate: {
69261                             x: x,
69262                             y: y
69263                         }
69264                     }, true);
69265                 }
69266             }
69267             //animate shadows
69268             if (me.chart.shadow && item.shadows) {
69269                 i = 0;
69270                 shadows = item.shadows;
69271                 ln = shadows.length;
69272                 for (; i < ln; i++) {
69273                     shadow = shadows[i];
69274                     to = {};
69275                     itemHighlightSegment = item.sprite._from.segment;
69276                     for (prop in itemHighlightSegment) {
69277                         if (! (prop in highlightSegment)) {
69278                             to[prop] = itemHighlightSegment[prop];
69279                         }
69280                     }
69281                     attrs = {
69282                         segment: Ext.applyIf(to, me.highlightCfg.segment)
69283                     };
69284                     if (animate) {
69285                         shadow.stopAnimation();
69286                         shadow.animate({
69287                             to: attrs,
69288                             duration: me.highlightDuration
69289                         });
69290                     }
69291                     else {
69292                         shadow.setAttributes(attrs, true);
69293                     }
69294                 }
69295             }
69296         }
69297     },
69298
69299     /**
69300      * un-highlights the specified item. If no item is provided it will un-highlight the entire series.
69301      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
69302      */
69303     unHighlightItem: function() {
69304         var me = this;
69305         if (!me.highlight) {
69306             return;
69307         }
69308
69309         if (('segment' in me.highlightCfg) && me.items) {
69310             var items = me.items,
69311                 animate = me.chart.animate,
69312                 shadowsEnabled = !!me.chart.shadow,
69313                 group = me.labelsGroup,
69314                 len = items.length,
69315                 i = 0,
69316                 j = 0,
69317                 display = me.label.display,
69318                 shadowLen, p, to, ihs, hs, sprite, shadows, shadow, item, label, attrs;
69319
69320             for (; i < len; i++) {
69321                 item = items[i];
69322                 if (!item) {
69323                     continue;
69324                 }
69325                 sprite = item.sprite;
69326                 if (sprite && sprite._highlighted) {
69327                     //animate labels
69328                     if (group) {
69329                         label = group.getAt(item.index);
69330                         attrs = Ext.apply({
69331                             translate: {
69332                                 x: 0,
69333                                 y: 0
69334                             }
69335                         },
69336                         display == 'rotate' ? {
69337                             rotate: {
69338                                 x: label.attr.x,
69339                                 y: label.attr.y,
69340                                 degrees: label.attr.rotation.degrees
69341                             }
69342                         }: {});
69343                         if (animate) {
69344                             label.stopAnimation();
69345                             label.animate({
69346                                 to: attrs,
69347                                 duration: me.highlightDuration
69348                             });
69349                         }
69350                         else {
69351                             label.setAttributes(attrs, true);
69352                         }
69353                     }
69354                     if (shadowsEnabled) {
69355                         shadows = item.shadows;
69356                         shadowLen = shadows.length;
69357                         for (; j < shadowLen; j++) {
69358                             to = {};
69359                             ihs = item.sprite._to.segment;
69360                             hs = item.sprite._from.segment;
69361                             Ext.apply(to, hs);
69362                             for (p in ihs) {
69363                                 if (! (p in hs)) {
69364                                     to[p] = ihs[p];
69365                                 }
69366                             }
69367                             shadow = shadows[j];
69368                             if (animate) {
69369                                 shadow.stopAnimation();
69370                                 shadow.animate({
69371                                     to: {
69372                                         segment: to
69373                                     },
69374                                     duration: me.highlightDuration
69375                                 });
69376                             }
69377                             else {
69378                                 shadow.setAttributes({ segment: to }, true);
69379                             }
69380                         }
69381                     }
69382                 }
69383             }
69384         }
69385         me.callParent(arguments);
69386     },
69387     
69388     /**
69389      * Returns the color of the series (to be displayed as color for the series legend item).
69390      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
69391      */
69392     getLegendColor: function(index) {
69393         var me = this;
69394         return me.colorArrayStyle[index % me.colorArrayStyle.length];
69395     }
69396 });
69397
69398
69399 /**
69400  * @class Ext.chart.series.Radar
69401  * @extends Ext.chart.series.Series
69402  * 
69403  * Creates a Radar Chart. A Radar Chart is a useful visualization technique for comparing different quantitative values for 
69404  * a constrained number of categories.
69405  * As with all other series, the Radar series must be appended in the *series* Chart array configuration. See the Chart 
69406  * documentation for more information. A typical configuration object for the radar series could be:
69407  * 
69408  {@img Ext.chart.series.Radar/Ext.chart.series.Radar.png Ext.chart.series.Radar chart series}  
69409   <pre><code>
69410     var store = Ext.create('Ext.data.JsonStore', {
69411         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
69412         data: [
69413             {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
69414             {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
69415             {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
69416             {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
69417             {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}                                                
69418         ]
69419     });
69420     
69421     Ext.create('Ext.chart.Chart', {
69422         renderTo: Ext.getBody(),
69423         width: 500,
69424         height: 300,
69425         animate: true,
69426         theme:'Category2',
69427         store: store,
69428         axes: [{
69429             type: 'Radial',
69430             position: 'radial',
69431             label: {
69432                 display: true
69433             }
69434         }],
69435         series: [{
69436             type: 'radar',
69437             xField: 'name',
69438             yField: 'data3',
69439             showInLegend: true,
69440             showMarkers: true,
69441             markerConfig: {
69442                 radius: 5,
69443                 size: 5           
69444             },
69445             style: {
69446                 'stroke-width': 2,
69447                 fill: 'none'
69448             }
69449         },{
69450             type: 'radar',
69451             xField: 'name',
69452             yField: 'data2',
69453             showMarkers: true,
69454             showInLegend: true,
69455             markerConfig: {
69456                 radius: 5,
69457                 size: 5
69458             },
69459             style: {
69460                 'stroke-width': 2,
69461                 fill: 'none'
69462             }
69463         },{
69464             type: 'radar',
69465             xField: 'name',
69466             yField: 'data5',
69467             showMarkers: true,
69468             showInLegend: true,
69469             markerConfig: {
69470                 radius: 5,
69471                 size: 5
69472             },
69473             style: {
69474                 'stroke-width': 2,
69475                 fill: 'none'
69476             }
69477         }]    
69478     });
69479    </code></pre>
69480  * 
69481  * 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,
69482  * `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 
69483  * the markerConfig object. Finally we override some theme styling properties by adding properties to the `style` object.
69484  * 
69485  * @xtype radar
69486  * 
69487  */
69488 Ext.define('Ext.chart.series.Radar', {
69489
69490     /* Begin Definitions */
69491
69492     extend: 'Ext.chart.series.Series',
69493
69494     requires: ['Ext.chart.Shape', 'Ext.fx.Anim'],
69495
69496     /* End Definitions */
69497
69498     type: "radar",
69499     alias: 'series.radar',
69500
69501     
69502     rad: Math.PI / 180,
69503
69504     showInLegend: false,
69505
69506     /**
69507      * @cfg {Object} style
69508      * An object containing styles for overriding series styles from Theming.
69509      */
69510     style: {},
69511     
69512     constructor: function(config) {
69513         this.callParent(arguments);
69514         var me = this,
69515             surface = me.chart.surface, i, l;
69516         me.group = surface.getGroup(me.seriesId);
69517         if (me.showMarkers) {
69518             me.markerGroup = surface.getGroup(me.seriesId + '-markers');
69519         }
69520     },
69521
69522     /**
69523      * Draws the series for the current chart.
69524      */
69525     drawSeries: function() {
69526         var me = this,
69527             store = me.chart.substore || me.chart.store,
69528             group = me.group,
69529             sprite,
69530             chart = me.chart,
69531             animate = chart.animate,
69532             field = me.field || me.yField,
69533             surface = chart.surface,
69534             chartBBox = chart.chartBBox,
69535             rendererAttributes,
69536             centerX, centerY,
69537             items,
69538             radius,
69539             maxValue = 0,
69540             fields = [],
69541             max = Math.max,
69542             cos = Math.cos,
69543             sin = Math.sin,
69544             pi2 = Math.PI * 2,
69545             l = store.getCount(),
69546             startPath, path, x, y, rho,
69547             i, nfields,
69548             seriesStyle = me.seriesStyle,
69549             seriesLabelStyle = me.seriesLabelStyle,
69550             first = chart.resizing || !me.radar,
69551             axis = chart.axes && chart.axes.get(0),
69552             aggregate = !(axis && axis.maximum);
69553         
69554         me.setBBox();
69555
69556         maxValue = aggregate? 0 : (axis.maximum || 0);
69557         
69558         Ext.apply(seriesStyle, me.style || {});
69559         
69560         //if the store is empty then there's nothing to draw
69561         if (!store || !store.getCount()) {
69562             return;
69563         }
69564         
69565         me.unHighlightItem();
69566         me.cleanHighlights();
69567
69568         centerX = me.centerX = chartBBox.x + (chartBBox.width / 2);
69569         centerY = me.centerY = chartBBox.y + (chartBBox.height / 2);
69570         me.radius = radius = Math.min(chartBBox.width, chartBBox.height) /2;
69571         me.items = items = [];
69572
69573         if (aggregate) {
69574             //get all renderer fields
69575             chart.series.each(function(series) {
69576                 fields.push(series.yField);
69577             });
69578             //get maxValue to interpolate
69579             store.each(function(record, i) {
69580                 for (i = 0, nfields = fields.length; i < nfields; i++) {
69581                     maxValue = max(+record.get(fields[i]), maxValue);
69582                 }
69583             });
69584         }
69585         //ensure non-zero value.
69586         maxValue = maxValue || 1;
69587         //create path and items
69588         startPath = []; path = [];
69589         store.each(function(record, i) {
69590             rho = radius * record.get(field) / maxValue;
69591             x = rho * cos(i / l * pi2);
69592             y = rho * sin(i / l * pi2);
69593             if (i == 0) {
69594                 path.push('M', x + centerX, y + centerY);
69595                 startPath.push('M', 0.01 * x + centerX, 0.01 * y + centerY);
69596             } else {
69597                 path.push('L', x + centerX, y + centerY);
69598                 startPath.push('L', 0.01 * x + centerX, 0.01 * y + centerY);
69599             }
69600             items.push({
69601                 sprite: false, //TODO(nico): add markers
69602                 point: [centerX + x, centerY + y],
69603                 series: me
69604             });
69605         });
69606         path.push('Z');
69607         //create path sprite
69608         if (!me.radar) {
69609             me.radar = surface.add(Ext.apply({
69610                 type: 'path',
69611                 group: group,
69612                 path: startPath
69613             }, seriesStyle || {}));
69614         }
69615         //reset on resizing
69616         if (chart.resizing) {
69617             me.radar.setAttributes({
69618                 path: startPath
69619             }, true);
69620         }
69621         //render/animate
69622         if (chart.animate) {
69623             me.onAnimate(me.radar, {
69624                 to: Ext.apply({
69625                     path: path
69626                 }, seriesStyle || {})
69627             });
69628         } else {
69629             me.radar.setAttributes(Ext.apply({
69630                 path: path
69631             }, seriesStyle || {}), true);
69632         }
69633         //render markers, labels and callouts
69634         if (me.showMarkers) {
69635             me.drawMarkers();
69636         }
69637         me.renderLabels();
69638         me.renderCallouts();
69639     },
69640     
69641     // @private draws the markers for the lines (if any).
69642     drawMarkers: function() {
69643         var me = this,
69644             chart = me.chart,
69645             surface = chart.surface,
69646             markerStyle = Ext.apply({}, me.markerStyle || {}),
69647             endMarkerStyle = Ext.apply(markerStyle, me.markerConfig),
69648             items = me.items, 
69649             type = endMarkerStyle.type,
69650             markerGroup = me.markerGroup,
69651             centerX = me.centerX,
69652             centerY = me.centerY,
69653             item, i, l, marker;
69654         
69655         delete endMarkerStyle.type;
69656         
69657         for (i = 0, l = items.length; i < l; i++) {
69658             item = items[i];
69659             marker = markerGroup.getAt(i);
69660             if (!marker) {
69661                 marker = Ext.chart.Shape[type](surface, Ext.apply({
69662                     group: markerGroup,
69663                     x: 0,
69664                     y: 0,
69665                     translate: {
69666                         x: centerX,
69667                         y: centerY
69668                     }
69669                 }, endMarkerStyle));
69670             }
69671             else {
69672                 marker.show();
69673             }
69674             if (chart.resizing) {
69675                 marker.setAttributes({
69676                     x: 0,
69677                     y: 0,
69678                     translate: {
69679                         x: centerX,
69680                         y: centerY
69681                     }
69682                 }, true);
69683             }
69684             marker._to = {
69685                 translate: {
69686                     x: item.point[0],
69687                     y: item.point[1]
69688                 }
69689             };
69690             //render/animate
69691             if (chart.animate) {
69692                 me.onAnimate(marker, {
69693                     to: marker._to
69694                 });
69695             }
69696             else {
69697                 marker.setAttributes(Ext.apply(marker._to, endMarkerStyle || {}), true);
69698             }
69699         }
69700     },
69701     
69702     isItemInPoint: function(x, y, item) {
69703         var point,
69704             tolerance = 10,
69705             abs = Math.abs;
69706         point = item.point;
69707         return (abs(point[0] - x) <= tolerance &&
69708                 abs(point[1] - y) <= tolerance);
69709     },
69710
69711     // @private callback for when creating a label sprite.
69712     onCreateLabel: function(storeItem, item, i, display) {
69713         var me = this,
69714             group = me.labelsGroup,
69715             config = me.label,
69716             centerX = me.centerX,
69717             centerY = me.centerY,
69718             point = item.point,
69719             endLabelStyle = Ext.apply(me.seriesLabelStyle || {}, config);
69720         
69721         return me.chart.surface.add(Ext.apply({
69722             'type': 'text',
69723             'text-anchor': 'middle',
69724             'group': group,
69725             'x': centerX,
69726             'y': centerY
69727         }, config || {}));
69728     },
69729
69730     // @private callback for when placing a label sprite.
69731     onPlaceLabel: function(label, storeItem, item, i, display, animate) {
69732         var me = this,
69733             chart = me.chart,
69734             resizing = chart.resizing,
69735             config = me.label,
69736             format = config.renderer,
69737             field = config.field,
69738             centerX = me.centerX,
69739             centerY = me.centerY,
69740             opt = {
69741                 x: item.point[0],
69742                 y: item.point[1]
69743             },
69744             x = opt.x - centerX,
69745             y = opt.y - centerY;
69746
69747         label.setAttributes({
69748             text: format(storeItem.get(field)),
69749             hidden: true
69750         },
69751         true);
69752         
69753         if (resizing) {
69754             label.setAttributes({
69755                 x: centerX,
69756                 y: centerY
69757             }, true);
69758         }
69759         
69760         if (animate) {
69761             label.show(true);
69762             me.onAnimate(label, {
69763                 to: opt
69764             });
69765         } else {
69766             label.setAttributes(opt, true);
69767             label.show(true);
69768         }
69769     },
69770
69771     // @private for toggling (show/hide) series. 
69772     toggleAll: function(show) {
69773         var me = this,
69774             i, ln, shadow, shadows;
69775         if (!show) {
69776             Ext.chart.series.Radar.superclass.hideAll.call(me);
69777         }
69778         else {
69779             Ext.chart.series.Radar.superclass.showAll.call(me);
69780         }
69781         if (me.radar) {
69782             me.radar.setAttributes({
69783                 hidden: !show
69784             }, true);
69785             //hide shadows too
69786             if (me.radar.shadows) {
69787                 for (i = 0, shadows = me.radar.shadows, ln = shadows.length; i < ln; i++) {
69788                     shadow = shadows[i];
69789                     shadow.setAttributes({
69790                         hidden: !show
69791                     }, true);
69792                 }
69793             }
69794         }
69795     },
69796     
69797     // @private hide all elements in the series.
69798     hideAll: function() {
69799         this.toggleAll(false);
69800         this.hideMarkers(0);
69801     },
69802     
69803     // @private show all elements in the series.
69804     showAll: function() {
69805         this.toggleAll(true);
69806     },
69807     
69808     // @private hide all markers that belong to `markerGroup`
69809     hideMarkers: function(index) {
69810         var me = this,
69811             count = me.markerGroup && me.markerGroup.getCount() || 0,
69812             i = index || 0;
69813         for (; i < count; i++) {
69814             me.markerGroup.getAt(i).hide(true);
69815         }
69816     }
69817 });
69818
69819
69820 /**
69821  * @class Ext.chart.series.Scatter
69822  * @extends Ext.chart.series.Cartesian
69823  * 
69824  * Creates a Scatter Chart. The scatter plot is useful when trying to display more than two variables in the same visualization. 
69825  * These variables can be mapped into x, y coordinates and also to an element's radius/size, color, etc.
69826  * As with all other series, the Scatter Series must be appended in the *series* Chart array configuration. See the Chart 
69827  * documentation for more information on creating charts. A typical configuration object for the scatter could be:
69828  *
69829 {@img Ext.chart.series.Scatter/Ext.chart.series.Scatter.png Ext.chart.series.Scatter chart series}  
69830   <pre><code>
69831     var store = Ext.create('Ext.data.JsonStore', {
69832         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
69833         data: [
69834             {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
69835             {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
69836             {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
69837             {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
69838             {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}                                                
69839         ]
69840     });
69841     
69842     Ext.create('Ext.chart.Chart', {
69843         renderTo: Ext.getBody(),
69844         width: 500,
69845         height: 300,
69846         animate: true,
69847         theme:'Category2',
69848         store: store,
69849         axes: [{
69850             type: 'Numeric',
69851             position: 'bottom',
69852             fields: ['data1', 'data2', 'data3'],
69853             title: 'Sample Values',
69854             grid: true,
69855             minimum: 0
69856         }, {
69857             type: 'Category',
69858             position: 'left',
69859             fields: ['name'],
69860             title: 'Sample Metrics'
69861         }],
69862         series: [{
69863             type: 'scatter',
69864             markerConfig: {
69865                 radius: 5,
69866                 size: 5
69867             },
69868             axis: 'left',
69869             xField: 'name',
69870             yField: 'data2'
69871         }, {
69872             type: 'scatter',
69873             markerConfig: {
69874                 radius: 5,
69875                 size: 5
69876             },
69877             axis: 'left',
69878             xField: 'name',
69879             yField: 'data3'
69880         }]   
69881     });
69882    </code></pre>
69883  
69884  * 
69885  * 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, 
69886  * `data1`, `data2` and `data3` respectively. All x-fields for the series must be the same field, in this case `name`. 
69887  * Each scatter series has a different styling configuration for markers, specified by the `markerConfig` object. Finally we set the left axis as 
69888  * axis to show the current values of the elements.
69889  * 
69890  * @xtype scatter
69891  * 
69892  */
69893 Ext.define('Ext.chart.series.Scatter', {
69894
69895     /* Begin Definitions */
69896
69897     extend: 'Ext.chart.series.Cartesian',
69898
69899     requires: ['Ext.chart.axis.Axis', 'Ext.chart.Shape', 'Ext.fx.Anim'],
69900
69901     /* End Definitions */
69902
69903     type: 'scatter',
69904     alias: 'series.scatter',
69905
69906     /**
69907      * @cfg {Object} markerConfig
69908      * The display style for the scatter series markers.
69909      */
69910     
69911     /**
69912      * @cfg {Object} style 
69913      * Append styling properties to this object for it to override theme properties.
69914      */
69915
69916     constructor: function(config) {
69917         this.callParent(arguments);
69918         var me = this,
69919             shadow = me.chart.shadow,
69920             surface = me.chart.surface, i, l;
69921         Ext.apply(me, config, {
69922             style: {},
69923             markerConfig: {},
69924             shadowAttributes: [{
69925                 "stroke-width": 6,
69926                 "stroke-opacity": 0.05,
69927                 stroke: 'rgb(0, 0, 0)'
69928             }, {
69929                 "stroke-width": 4,
69930                 "stroke-opacity": 0.1,
69931                 stroke: 'rgb(0, 0, 0)'
69932             }, {
69933                 "stroke-width": 2,
69934                 "stroke-opacity": 0.15,
69935                 stroke: 'rgb(0, 0, 0)'
69936             }]
69937         });
69938         me.group = surface.getGroup(me.seriesId);
69939         if (shadow) {
69940             for (i = 0, l = me.shadowAttributes.length; i < l; i++) {
69941                 me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
69942             }
69943         }
69944     },
69945
69946     // @private Get chart and data boundaries
69947     getBounds: function() {
69948         var me = this,
69949             chart = me.chart,
69950             store = chart.substore || chart.store,
69951             axes = [].concat(me.axis),
69952             bbox, xScale, yScale, ln, minX, minY, maxX, maxY, i, axis, ends;
69953
69954         me.setBBox();
69955         bbox = me.bbox;
69956
69957         for (i = 0, ln = axes.length; i < ln; i++) { 
69958             axis = chart.axes.get(axes[i]);
69959             if (axis) {
69960                 ends = axis.calcEnds();
69961                 if (axis.position == 'top' || axis.position == 'bottom') {
69962                     minX = ends.from;
69963                     maxX = ends.to;
69964                 }
69965                 else {
69966                     minY = ends.from;
69967                     maxY = ends.to;
69968                 }
69969             }
69970         }
69971         // If a field was specified without a corresponding axis, create one to get bounds
69972         if (me.xField && !Ext.isNumber(minX)) {
69973             axis = Ext.create('Ext.chart.axis.Axis', {
69974                 chart: chart,
69975                 fields: [].concat(me.xField)
69976             }).calcEnds();
69977             minX = axis.from;
69978             maxX = axis.to;
69979         }
69980         if (me.yField && !Ext.isNumber(minY)) {
69981             axis = Ext.create('Ext.chart.axis.Axis', {
69982                 chart: chart,
69983                 fields: [].concat(me.yField)
69984             }).calcEnds();
69985             minY = axis.from;
69986             maxY = axis.to;
69987         }
69988
69989         if (isNaN(minX)) {
69990             minX = 0;
69991             maxX = store.getCount() - 1;
69992             xScale = bbox.width / (store.getCount() - 1);
69993         }
69994         else {
69995             xScale = bbox.width / (maxX - minX);
69996         }
69997
69998         if (isNaN(minY)) {
69999             minY = 0;
70000             maxY = store.getCount() - 1;
70001             yScale = bbox.height / (store.getCount() - 1);
70002         } 
70003         else {
70004             yScale = bbox.height / (maxY - minY);
70005         }
70006
70007         return {
70008             bbox: bbox,
70009             minX: minX,
70010             minY: minY,
70011             xScale: xScale,
70012             yScale: yScale
70013         };
70014     },
70015
70016     // @private Build an array of paths for the chart
70017     getPaths: function() {
70018         var me = this,
70019             chart = me.chart,
70020             enableShadows = chart.shadow,
70021             store = chart.substore || chart.store,
70022             group = me.group,
70023             bounds = me.bounds = me.getBounds(),
70024             bbox = me.bbox,
70025             xScale = bounds.xScale,
70026             yScale = bounds.yScale,
70027             minX = bounds.minX,
70028             minY = bounds.minY,
70029             boxX = bbox.x,
70030             boxY = bbox.y,
70031             boxHeight = bbox.height,
70032             items = me.items = [],
70033             attrs = [],
70034             x, y, xValue, yValue, sprite;
70035
70036         store.each(function(record, i) {
70037             xValue = record.get(me.xField);
70038             yValue = record.get(me.yField);
70039             //skip undefined values
70040             if (typeof yValue == 'undefined' || (typeof yValue == 'string' && !yValue)) {
70041                 if (Ext.isDefined(Ext.global.console)) {
70042                     Ext.global.console.warn("[Ext.chart.series.Scatter]  Skipping a store element with an undefined value at ", record, xValue, yValue);
70043                 }
70044                 return;
70045             }
70046             // Ensure a value
70047             if (typeof xValue == 'string' || typeof xValue == 'object') {
70048                 xValue = i;
70049             }
70050             if (typeof yValue == 'string' || typeof yValue == 'object') {
70051                 yValue = i;
70052             }
70053             x = boxX + (xValue - minX) * xScale;
70054             y = boxY + boxHeight - (yValue - minY) * yScale;
70055             attrs.push({
70056                 x: x,
70057                 y: y
70058             });
70059
70060             me.items.push({
70061                 series: me,
70062                 value: [xValue, yValue],
70063                 point: [x, y],
70064                 storeItem: record
70065             });
70066
70067             // When resizing, reset before animating
70068             if (chart.animate && chart.resizing) {
70069                 sprite = group.getAt(i);
70070                 if (sprite) {
70071                     me.resetPoint(sprite);
70072                     if (enableShadows) {
70073                         me.resetShadow(sprite);
70074                     }
70075                 }
70076             }
70077         });
70078         return attrs;
70079     },
70080
70081     // @private translate point to the center
70082     resetPoint: function(sprite) {
70083         var bbox = this.bbox;
70084         sprite.setAttributes({
70085             translate: {
70086                 x: (bbox.x + bbox.width) / 2,
70087                 y: (bbox.y + bbox.height) / 2
70088             }
70089         }, true);
70090     },
70091
70092     // @private translate shadows of a sprite to the center
70093     resetShadow: function(sprite) {
70094         var me = this,
70095             shadows = sprite.shadows,
70096             shadowAttributes = me.shadowAttributes,
70097             ln = me.shadowGroups.length,
70098             bbox = me.bbox,
70099             i, attr;
70100         for (i = 0; i < ln; i++) {
70101             attr = Ext.apply({}, shadowAttributes[i]);
70102             if (attr.translate) {
70103                 attr.translate.x += (bbox.x + bbox.width) / 2;
70104                 attr.translate.y += (bbox.y + bbox.height) / 2;
70105             }
70106             else {
70107                 attr.translate = {
70108                     x: (bbox.x + bbox.width) / 2,
70109                     y: (bbox.y + bbox.height) / 2
70110                 };
70111             }
70112             shadows[i].setAttributes(attr, true);
70113         }
70114     },
70115
70116     // @private create a new point
70117     createPoint: function(attr, type) {
70118         var me = this,
70119             chart = me.chart,
70120             group = me.group,
70121             bbox = me.bbox;
70122
70123         return Ext.chart.Shape[type](chart.surface, Ext.apply({}, {
70124             x: 0,
70125             y: 0,
70126             group: group,
70127             translate: {
70128                 x: (bbox.x + bbox.width) / 2,
70129                 y: (bbox.y + bbox.height) / 2
70130             }
70131         }, attr));
70132     },
70133
70134     // @private create a new set of shadows for a sprite
70135     createShadow: function(sprite, endMarkerStyle, type) {
70136         var me = this,
70137             chart = me.chart,
70138             shadowGroups = me.shadowGroups,
70139             shadowAttributes = me.shadowAttributes,
70140             lnsh = shadowGroups.length,
70141             bbox = me.bbox,
70142             i, shadow, shadows, attr;
70143
70144         sprite.shadows = shadows = [];
70145
70146         for (i = 0; i < lnsh; i++) {
70147             attr = Ext.apply({}, shadowAttributes[i]);
70148             if (attr.translate) {
70149                 attr.translate.x += (bbox.x + bbox.width) / 2;
70150                 attr.translate.y += (bbox.y + bbox.height) / 2;
70151             }
70152             else {
70153                 Ext.apply(attr, {
70154                     translate: {
70155                         x: (bbox.x + bbox.width) / 2,
70156                         y: (bbox.y + bbox.height) / 2
70157                     }
70158                 });
70159             }
70160             Ext.apply(attr, endMarkerStyle);
70161             shadow = Ext.chart.Shape[type](chart.surface, Ext.apply({}, {
70162                 x: 0,
70163                 y: 0,
70164                 group: shadowGroups[i]
70165             }, attr));
70166             shadows.push(shadow);
70167         }
70168     },
70169
70170     /**
70171      * Draws the series for the current chart.
70172      */
70173     drawSeries: function() {
70174         var me = this,
70175             chart = me.chart,
70176             store = chart.substore || chart.store,
70177             group = me.group,
70178             enableShadows = chart.shadow,
70179             shadowGroups = me.shadowGroups,
70180             shadowAttributes = me.shadowAttributes,
70181             lnsh = shadowGroups.length,
70182             sprite, attrs, attr, ln, i, endMarkerStyle, shindex, type, shadows,
70183             rendererAttributes, shadowAttribute;
70184
70185         endMarkerStyle = Ext.apply(me.markerStyle, me.markerConfig);
70186         type = endMarkerStyle.type;
70187         delete endMarkerStyle.type;
70188
70189         //if the store is empty then there's nothing to be rendered
70190         if (!store || !store.getCount()) {
70191             return;
70192         }
70193
70194         me.unHighlightItem();
70195         me.cleanHighlights();
70196
70197         attrs = me.getPaths();
70198         ln = attrs.length;
70199         for (i = 0; i < ln; i++) {
70200             attr = attrs[i];
70201             sprite = group.getAt(i);
70202             Ext.apply(attr, endMarkerStyle);
70203
70204             // Create a new sprite if needed (no height)
70205             if (!sprite) {
70206                 sprite = me.createPoint(attr, type);
70207                 if (enableShadows) {
70208                     me.createShadow(sprite, endMarkerStyle, type);
70209                 }
70210             }
70211
70212             shadows = sprite.shadows;
70213             if (chart.animate) {
70214                 rendererAttributes = me.renderer(sprite, store.getAt(i), { translate: attr }, i, store);
70215                 sprite._to = rendererAttributes;
70216                 me.onAnimate(sprite, {
70217                     to: rendererAttributes
70218                 });
70219                 //animate shadows
70220                 for (shindex = 0; shindex < lnsh; shindex++) {
70221                     shadowAttribute = Ext.apply({}, shadowAttributes[shindex]);
70222                     rendererAttributes = me.renderer(shadows[shindex], store.getAt(i), Ext.apply({}, { 
70223                         translate: {
70224                             x: attr.x + (shadowAttribute.translate? shadowAttribute.translate.x : 0),
70225                             y: attr.y + (shadowAttribute.translate? shadowAttribute.translate.y : 0)
70226                         } 
70227                     }, shadowAttribute), i, store);
70228                     me.onAnimate(shadows[shindex], { to: rendererAttributes });
70229                 }
70230             }
70231             else {
70232                 rendererAttributes = me.renderer(sprite, store.getAt(i), Ext.apply({ translate: attr }, { hidden: false }), i, store);
70233                 sprite.setAttributes(rendererAttributes, true);
70234                 //update shadows
70235                 for (shindex = 0; shindex < lnsh; shindex++) {
70236                     shadowAttribute = shadowAttributes[shindex];
70237                     rendererAttributes = me.renderer(shadows[shindex], store.getAt(i), Ext.apply({ 
70238                         x: attr.x,
70239                         y: attr.y
70240                     }, shadowAttribute), i, store);
70241                     shadows[shindex].setAttributes(rendererAttributes, true);
70242                 }
70243             }
70244             me.items[i].sprite = sprite;
70245         }
70246
70247         // Hide unused sprites
70248         ln = group.getCount();
70249         for (i = attrs.length; i < ln; i++) {
70250             group.getAt(i).hide(true);
70251         }
70252         me.renderLabels();
70253         me.renderCallouts();
70254     },
70255     
70256     // @private callback for when creating a label sprite.
70257     onCreateLabel: function(storeItem, item, i, display) {
70258         var me = this,
70259             group = me.labelsGroup,
70260             config = me.label,
70261             endLabelStyle = Ext.apply({}, config, me.seriesLabelStyle),
70262             bbox = me.bbox;
70263         
70264         return me.chart.surface.add(Ext.apply({
70265             type: 'text',
70266             group: group,
70267             x: item.point[0],
70268             y: bbox.y + bbox.height / 2
70269         }, endLabelStyle));
70270     },
70271     
70272     // @private callback for when placing a label sprite.
70273     onPlaceLabel: function(label, storeItem, item, i, display, animate) {
70274         var me = this,
70275             chart = me.chart,
70276             resizing = chart.resizing,
70277             config = me.label,
70278             format = config.renderer,
70279             field = config.field,
70280             bbox = me.bbox,
70281             x = item.point[0],
70282             y = item.point[1],
70283             radius = item.sprite.attr.radius,
70284             bb, width, height, anim;
70285         
70286         label.setAttributes({
70287             text: format(storeItem.get(field)),
70288             hidden: true
70289         }, true);
70290         
70291         if (display == 'rotate') {
70292             label.setAttributes({
70293                 'text-anchor': 'start',
70294                 'rotation': {
70295                     x: x,
70296                     y: y,
70297                     degrees: -45
70298                 }
70299             }, true);
70300             //correct label position to fit into the box
70301             bb = label.getBBox();
70302             width = bb.width;
70303             height = bb.height;
70304             x = x < bbox.x? bbox.x : x;
70305             x = (x + width > bbox.x + bbox.width)? (x - (x + width - bbox.x - bbox.width)) : x;
70306             y = (y - height < bbox.y)? bbox.y + height : y;
70307         
70308         } else if (display == 'under' || display == 'over') {
70309             //TODO(nicolas): find out why width/height values in circle bounding boxes are undefined.
70310             bb = item.sprite.getBBox();
70311             bb.width = bb.width || (radius * 2);
70312             bb.height = bb.height || (radius * 2);
70313             y = y + (display == 'over'? -bb.height : bb.height);
70314             //correct label position to fit into the box
70315             bb = label.getBBox();
70316             width = bb.width/2;
70317             height = bb.height/2;
70318             x = x - width < bbox.x ? bbox.x + width : x;
70319             x = (x + width > bbox.x + bbox.width) ? (x - (x + width - bbox.x - bbox.width)) : x;
70320             y = y - height < bbox.y? bbox.y + height : y;
70321             y = (y + height > bbox.y + bbox.height) ? (y - (y + height - bbox.y - bbox.height)) : y;
70322         }
70323
70324         if (!chart.animate) {
70325             label.setAttributes({
70326                 x: x,
70327                 y: y
70328             }, true);
70329             label.show(true);
70330         }
70331         else {
70332             if (resizing) {
70333                 anim = item.sprite.getActiveAnimation();
70334                 if (anim) {
70335                     anim.on('afteranimate', function() {
70336                         label.setAttributes({
70337                             x: x,
70338                             y: y
70339                         }, true);
70340                         label.show(true);
70341                     });   
70342                 }
70343                 else {
70344                     label.show(true);
70345                 }
70346             }
70347             else {
70348                 me.onAnimate(label, {
70349                     to: {
70350                         x: x,
70351                         y: y
70352                     }
70353                 });
70354             }
70355         }
70356     },
70357     
70358     // @private callback for when placing a callout sprite.    
70359     onPlaceCallout: function(callout, storeItem, item, i, display, animate, index) {
70360         var me = this,
70361             chart = me.chart,
70362             surface = chart.surface,
70363             resizing = chart.resizing,
70364             config = me.callouts,
70365             items = me.items,
70366             cur = item.point,
70367             normal,
70368             bbox = callout.label.getBBox(),
70369             offsetFromViz = 30,
70370             offsetToSide = 10,
70371             offsetBox = 3,
70372             boxx, boxy, boxw, boxh,
70373             p, clipRect = me.bbox,
70374             x, y;
70375     
70376         //position
70377         normal = [Math.cos(Math.PI /4), -Math.sin(Math.PI /4)];
70378         x = cur[0] + normal[0] * offsetFromViz;
70379         y = cur[1] + normal[1] * offsetFromViz;
70380         
70381         //box position and dimensions
70382         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
70383         boxy = y - bbox.height /2 - offsetBox;
70384         boxw = bbox.width + 2 * offsetBox;
70385         boxh = bbox.height + 2 * offsetBox;
70386         
70387         //now check if we're out of bounds and invert the normal vector correspondingly
70388         //this may add new overlaps between labels (but labels won't be out of bounds).
70389         if (boxx < clipRect[0] || (boxx + boxw) > (clipRect[0] + clipRect[2])) {
70390             normal[0] *= -1;
70391         }
70392         if (boxy < clipRect[1] || (boxy + boxh) > (clipRect[1] + clipRect[3])) {
70393             normal[1] *= -1;
70394         }
70395     
70396         //update positions
70397         x = cur[0] + normal[0] * offsetFromViz;
70398         y = cur[1] + normal[1] * offsetFromViz;
70399         
70400         //update box position and dimensions
70401         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
70402         boxy = y - bbox.height /2 - offsetBox;
70403         boxw = bbox.width + 2 * offsetBox;
70404         boxh = bbox.height + 2 * offsetBox;
70405         
70406         if (chart.animate) {
70407             //set the line from the middle of the pie to the box.
70408             me.onAnimate(callout.lines, {
70409                 to: {
70410                     path: ["M", cur[0], cur[1], "L", x, y, "Z"]
70411                 }
70412             }, true);
70413             //set box position
70414             me.onAnimate(callout.box, {
70415                 to: {
70416                     x: boxx,
70417                     y: boxy,
70418                     width: boxw,
70419                     height: boxh
70420                 }
70421             }, true);
70422             //set text position
70423             me.onAnimate(callout.label, {
70424                 to: {
70425                     x: x + (normal[0] > 0? offsetBox : -(bbox.width + offsetBox)),
70426                     y: y
70427                 }
70428             }, true);
70429         } else {
70430             //set the line from the middle of the pie to the box.
70431             callout.lines.setAttributes({
70432                 path: ["M", cur[0], cur[1], "L", x, y, "Z"]
70433             }, true);
70434             //set box position
70435             callout.box.setAttributes({
70436                 x: boxx,
70437                 y: boxy,
70438                 width: boxw,
70439                 height: boxh
70440             }, true);
70441             //set text position
70442             callout.label.setAttributes({
70443                 x: x + (normal[0] > 0? offsetBox : -(bbox.width + offsetBox)),
70444                 y: y
70445             }, true);
70446         }
70447         for (p in callout) {
70448             callout[p].show(true);
70449         }
70450     },
70451
70452     // @private handles sprite animation for the series.
70453     onAnimate: function(sprite, attr) {
70454         sprite.show();
70455         return this.callParent(arguments);
70456     },
70457
70458     isItemInPoint: function(x, y, item) {
70459         var point,
70460             tolerance = 10,
70461             abs = Math.abs;
70462
70463         function dist(point) {
70464             var dx = abs(point[0] - x),
70465                 dy = abs(point[1] - y);
70466             return Math.sqrt(dx * dx + dy * dy);
70467         }
70468         point = item.point;
70469         return (point[0] - tolerance <= x && point[0] + tolerance >= x &&
70470             point[1] - tolerance <= y && point[1] + tolerance >= y);
70471     }
70472 });
70473
70474
70475 /**
70476  * @class Ext.chart.theme.Base
70477  * Provides default colors for non-specified things. Should be sub-classed when creating new themes.
70478  * @ignore
70479  */
70480 Ext.define('Ext.chart.theme.Base', {
70481
70482     /* Begin Definitions */
70483
70484     requires: ['Ext.chart.theme.Theme'],
70485
70486     /* End Definitions */
70487
70488     constructor: function(config) {
70489         Ext.chart.theme.call(this, config, {
70490             background: false,
70491             axis: {
70492                 stroke: '#444',
70493                 'stroke-width': 1
70494             },
70495             axisLabelTop: {
70496                 fill: '#444',
70497                 font: '12px Arial, Helvetica, sans-serif',
70498                 spacing: 2,
70499                 padding: 5,
70500                 renderer: function(v) { return v; }
70501             },
70502             axisLabelRight: {
70503                 fill: '#444',
70504                 font: '12px Arial, Helvetica, sans-serif',
70505                 spacing: 2,
70506                 padding: 5,
70507                 renderer: function(v) { return v; }
70508             },
70509             axisLabelBottom: {
70510                 fill: '#444',
70511                 font: '12px Arial, Helvetica, sans-serif',
70512                 spacing: 2,
70513                 padding: 5,
70514                 renderer: function(v) { return v; }
70515             },
70516             axisLabelLeft: {
70517                 fill: '#444',
70518                 font: '12px Arial, Helvetica, sans-serif',
70519                 spacing: 2,
70520                 padding: 5,
70521                 renderer: function(v) { return v; }
70522             },
70523             axisTitleTop: {
70524                 font: 'bold 18px Arial',
70525                 fill: '#444'
70526             },
70527             axisTitleRight: {
70528                 font: 'bold 18px Arial',
70529                 fill: '#444',
70530                 rotate: {
70531                     x:0, y:0,
70532                     degrees: 270
70533                 }
70534             },
70535             axisTitleBottom: {
70536                 font: 'bold 18px Arial',
70537                 fill: '#444'
70538             },
70539             axisTitleLeft: {
70540                 font: 'bold 18px Arial',
70541                 fill: '#444',
70542                 rotate: {
70543                     x:0, y:0,
70544                     degrees: 270
70545                 }
70546             },
70547             series: {
70548                 'stroke-width': 0
70549             },
70550             seriesLabel: {
70551                 font: '12px Arial',
70552                 fill: '#333'
70553             },
70554             marker: {
70555                 stroke: '#555',
70556                 fill: '#000',
70557                 radius: 3,
70558                 size: 3
70559             },
70560             colors: [ "#94ae0a", "#115fa6","#a61120", "#ff8809", "#ffd13e", "#a61187", "#24ad9a", "#7c7474", "#a66111"],
70561             seriesThemes: [{
70562                 fill: "#115fa6"
70563             }, {
70564                 fill: "#94ae0a"
70565             }, {
70566                 fill: "#a61120"
70567             }, {
70568                 fill: "#ff8809"
70569             }, {
70570                 fill: "#ffd13e"
70571             }, {
70572                 fill: "#a61187"
70573             }, {
70574                 fill: "#24ad9a"
70575             }, {
70576                 fill: "#7c7474"
70577             }, {
70578                 fill: "#a66111"
70579             }],
70580             markerThemes: [{
70581                 fill: "#115fa6",
70582                 type: 'circle' 
70583             }, {
70584                 fill: "#94ae0a",
70585                 type: 'cross'
70586             }, {
70587                 fill: "#a61120",
70588                 type: 'plus'
70589             }]
70590         });
70591     }
70592 }, function() {
70593     var palette = ['#b1da5a', '#4ce0e7', '#e84b67', '#da5abd', '#4d7fe6', '#fec935'],
70594         names = ['Green', 'Sky', 'Red', 'Purple', 'Blue', 'Yellow'],
70595         i = 0, j = 0, l = palette.length, themes = Ext.chart.theme,
70596         categories = [['#f0a50a', '#c20024', '#2044ba', '#810065', '#7eae29'],
70597                       ['#6d9824', '#87146e', '#2a9196', '#d39006', '#1e40ac'],
70598                       ['#fbbc29', '#ce2e4e', '#7e0062', '#158b90', '#57880e'],
70599                       ['#ef5773', '#fcbd2a', '#4f770d', '#1d3eaa', '#9b001f'],
70600                       ['#7eae29', '#fdbe2a', '#910019', '#27b4bc', '#d74dbc'],
70601                       ['#44dce1', '#0b2592', '#996e05', '#7fb325', '#b821a1']],
70602         cats = categories.length;
70603     
70604     //Create themes from base colors
70605     for (; i < l; i++) {
70606         themes[names[i]] = (function(color) {
70607             return Ext.extend(themes.Base, {
70608                 constructor: function(config) {
70609                     themes.Base.prototype.constructor.call(this, Ext.apply({
70610                         baseColor: color
70611                     }, config));
70612                 }
70613             });
70614         })(palette[i]);
70615     }
70616     
70617     //Create theme from color array
70618     for (i = 0; i < cats; i++) {
70619         themes['Category' + (i + 1)] = (function(category) {
70620             return Ext.extend(themes.Base, {
70621                 constructor: function(config) {
70622                     themes.Base.prototype.constructor.call(this, Ext.apply({
70623                         colors: category
70624                     }, config));
70625                 }
70626             });
70627         })(categories[i]);
70628     }
70629 });
70630
70631 /**
70632  * @author Ed Spencer
70633  * @class Ext.data.ArrayStore
70634  * @extends Ext.data.Store
70635  * @ignore
70636  *
70637  * <p>Small helper class to make creating {@link Ext.data.Store}s from Array data easier.
70638  * An ArrayStore will be automatically configured with a {@link Ext.data.reader.Array}.</p>
70639  *
70640  * <p>A store configuration would be something like:</p>
70641 <pre><code>
70642 var store = new Ext.data.ArrayStore({
70643     // store configs
70644     autoDestroy: true,
70645     storeId: 'myStore',
70646     // reader configs
70647     idIndex: 0,
70648     fields: [
70649        'company',
70650        {name: 'price', type: 'float'},
70651        {name: 'change', type: 'float'},
70652        {name: 'pctChange', type: 'float'},
70653        {name: 'lastChange', type: 'date', dateFormat: 'n/j h:ia'}
70654     ]
70655 });
70656 </code></pre>
70657  * <p>This store is configured to consume a returned object of the form:
70658 <pre><code>
70659 var myData = [
70660     ['3m Co',71.72,0.02,0.03,'9/1 12:00am'],
70661     ['Alcoa Inc',29.01,0.42,1.47,'9/1 12:00am'],
70662     ['Boeing Co.',75.43,0.53,0.71,'9/1 12:00am'],
70663     ['Hewlett-Packard Co.',36.53,-0.03,-0.08,'9/1 12:00am'],
70664     ['Wal-Mart Stores, Inc.',45.45,0.73,1.63,'9/1 12:00am']
70665 ];
70666 </code></pre>
70667 *
70668  * <p>An object literal of this form could also be used as the {@link #data} config option.</p>
70669  *
70670  * <p><b>*Note:</b> Although not listed here, this class accepts all of the configuration options of
70671  * <b>{@link Ext.data.reader.Array ArrayReader}</b>.</p>
70672  *
70673  * @constructor
70674  * @param {Object} config
70675  * @xtype arraystore
70676  */
70677 Ext.define('Ext.data.ArrayStore', {
70678     extend: 'Ext.data.Store',
70679     alias: 'store.array',
70680     uses: ['Ext.data.reader.Array'],
70681
70682     /**
70683      * @cfg {Ext.data.DataReader} reader @hide
70684      */
70685     constructor: function(config) {
70686         config = config || {};
70687
70688         Ext.applyIf(config, {
70689             proxy: {
70690                 type: 'memory',
70691                 reader: 'array'
70692             }
70693         });
70694
70695         this.callParent([config]);
70696     },
70697
70698     loadData: function(data, append) {
70699         if (this.expandData === true) {
70700             var r = [],
70701                 i = 0,
70702                 ln = data.length;
70703
70704             for (; i < ln; i++) {
70705                 r[r.length] = [data[i]];
70706             }
70707
70708             data = r;
70709         }
70710
70711         this.callParent([data, append]);
70712     }
70713 }, function() {
70714     // backwards compat
70715     Ext.data.SimpleStore = Ext.data.ArrayStore;
70716     // Ext.reg('simplestore', Ext.data.SimpleStore);
70717 });
70718
70719 /**
70720  * @author Ed Spencer
70721  * @class Ext.data.Batch
70722  * 
70723  * <p>Provides a mechanism to run one or more {@link Ext.data.Operation operations} in a given order. Fires the 'operationcomplete' event
70724  * after the completion of each Operation, and the 'complete' event when all Operations have been successfully executed. Fires an 'exception'
70725  * event if any of the Operations encounter an exception.</p>
70726  * 
70727  * <p>Usually these are only used internally by {@link Ext.data.proxy.Proxy} classes</p>
70728  * 
70729  * @constructor
70730  * @param {Object} config Optional config object
70731  */
70732 Ext.define('Ext.data.Batch', {
70733     mixins: {
70734         observable: 'Ext.util.Observable'
70735     },
70736     
70737     /**
70738      * True to immediately start processing the batch as soon as it is constructed (defaults to false)
70739      * @property autoStart
70740      * @type Boolean
70741      */
70742     autoStart: false,
70743     
70744     /**
70745      * The index of the current operation being executed
70746      * @property current
70747      * @type Number
70748      */
70749     current: -1,
70750     
70751     /**
70752      * The total number of operations in this batch. Read only
70753      * @property total
70754      * @type Number
70755      */
70756     total: 0,
70757     
70758     /**
70759      * True if the batch is currently running
70760      * @property isRunning
70761      * @type Boolean
70762      */
70763     isRunning: false,
70764     
70765     /**
70766      * True if this batch has been executed completely
70767      * @property isComplete
70768      * @type Boolean
70769      */
70770     isComplete: false,
70771     
70772     /**
70773      * True if this batch has encountered an exception. This is cleared at the start of each operation
70774      * @property hasException
70775      * @type Boolean
70776      */
70777     hasException: false,
70778     
70779     /**
70780      * True to automatically pause the execution of the batch if any operation encounters an exception (defaults to true)
70781      * @property pauseOnException
70782      * @type Boolean
70783      */
70784     pauseOnException: true,
70785     
70786     constructor: function(config) {   
70787         var me = this;
70788                      
70789         me.addEvents(
70790           /**
70791            * @event complete
70792            * Fired when all operations of this batch have been completed
70793            * @param {Ext.data.Batch} batch The batch object
70794            * @param {Object} operation The last operation that was executed
70795            */
70796           'complete',
70797           
70798           /**
70799            * @event exception
70800            * Fired when a operation encountered an exception
70801            * @param {Ext.data.Batch} batch The batch object
70802            * @param {Object} operation The operation that encountered the exception
70803            */
70804           'exception',
70805           
70806           /**
70807            * @event operationcomplete
70808            * Fired when each operation of the batch completes
70809            * @param {Ext.data.Batch} batch The batch object
70810            * @param {Object} operation The operation that just completed
70811            */
70812           'operationcomplete'
70813         );
70814         
70815         me.mixins.observable.constructor.call(me, config);
70816         
70817         /**
70818          * Ordered array of operations that will be executed by this batch
70819          * @property operations
70820          * @type Array
70821          */
70822         me.operations = [];
70823     },
70824     
70825     /**
70826      * Adds a new operation to this batch
70827      * @param {Object} operation The {@link Ext.data.Operation Operation} object
70828      */
70829     add: function(operation) {
70830         this.total++;
70831         
70832         operation.setBatch(this);
70833         
70834         this.operations.push(operation);
70835     },
70836     
70837     /**
70838      * Kicks off the execution of the batch, continuing from the next operation if the previous
70839      * operation encountered an exception, or if execution was paused
70840      */
70841     start: function() {
70842         this.hasException = false;
70843         this.isRunning = true;
70844         
70845         this.runNextOperation();
70846     },
70847     
70848     /**
70849      * @private
70850      * Runs the next operation, relative to this.current.
70851      */
70852     runNextOperation: function() {
70853         this.runOperation(this.current + 1);
70854     },
70855     
70856     /**
70857      * Pauses execution of the batch, but does not cancel the current operation
70858      */
70859     pause: function() {
70860         this.isRunning = false;
70861     },
70862     
70863     /**
70864      * Executes a operation by its numeric index
70865      * @param {Number} index The operation index to run
70866      */
70867     runOperation: function(index) {
70868         var me = this,
70869             operations = me.operations,
70870             operation  = operations[index],
70871             onProxyReturn;
70872         
70873         if (operation === undefined) {
70874             me.isRunning  = false;
70875             me.isComplete = true;
70876             me.fireEvent('complete', me, operations[operations.length - 1]);
70877         } else {
70878             me.current = index;
70879             
70880             onProxyReturn = function(operation) {
70881                 var hasException = operation.hasException();
70882                 
70883                 if (hasException) {
70884                     me.hasException = true;
70885                     me.fireEvent('exception', me, operation);
70886                 } else {
70887                     me.fireEvent('operationcomplete', me, operation);
70888                 }
70889
70890                 if (hasException && me.pauseOnException) {
70891                     me.pause();
70892                 } else {
70893                     operation.setCompleted();
70894                     me.runNextOperation();
70895                 }
70896             };
70897             
70898             operation.setStarted();
70899             
70900             me.proxy[operation.action](operation, onProxyReturn, me);
70901         }
70902     }
70903 });
70904 /**
70905  * @author Ed Spencer
70906  * @class Ext.data.BelongsToAssociation
70907  * @extends Ext.data.Association
70908  *
70909  * <p>Represents a many to one association with another model. The owner model is expected to have
70910  * a foreign key which references the primary key of the associated model:</p>
70911  *
70912 <pre><code>
70913 Ext.define('Category', {
70914     extend: 'Ext.data.Model',
70915     fields: [
70916         {name: 'id',   type: 'int'},
70917         {name: 'name', type: 'string'}
70918     ]
70919 });
70920
70921 Ext.define('Product', {
70922     extend: 'Ext.data.Model',
70923     fields: [
70924         {name: 'id',          type: 'int'},
70925         {name: 'category_id', type: 'int'},
70926         {name: 'name',        type: 'string'}
70927     ],
70928     // we can use the belongsTo shortcut on the model to create a belongsTo association
70929     belongsTo: {type: 'belongsTo', model: 'Category'}
70930 });
70931 </code></pre>
70932  * <p>In the example above we have created models for Products and Categories, and linked them together
70933  * by saying that each Product belongs to a Category. This automatically links each Product to a Category
70934  * based on the Product's category_id, and provides new functions on the Product model:</p>
70935  *
70936  * <p><u>Generated getter function</u></p>
70937  *
70938  * <p>The first function that is added to the owner model is a getter function:</p>
70939  *
70940 <pre><code>
70941 var product = new Product({
70942     id: 100,
70943     category_id: 20,
70944     name: 'Sneakers'
70945 });
70946
70947 product.getCategory(function(category, operation) {
70948     //do something with the category object
70949     alert(category.get('id')); //alerts 20
70950 }, this);
70951 </code></pre>
70952 *
70953  * <p>The getCategory function was created on the Product model when we defined the association. This uses the
70954  * Category's configured {@link Ext.data.proxy.Proxy proxy} to load the Category asynchronously, calling the provided
70955  * callback when it has loaded.</p>
70956  *
70957  * <p>The new getCategory function will also accept an object containing success, failure and callback properties
70958  * - callback will always be called, success will only be called if the associated model was loaded successfully
70959  * and failure will only be called if the associatied model could not be loaded:</p>
70960  *
70961 <pre><code>
70962 product.getCategory({
70963     callback: function(category, operation) {}, //a function that will always be called
70964     success : function(category, operation) {}, //a function that will only be called if the load succeeded
70965     failure : function(category, operation) {}, //a function that will only be called if the load did not succeed
70966     scope   : this //optionally pass in a scope object to execute the callbacks in
70967 });
70968 </code></pre>
70969  *
70970  * <p>In each case above the callbacks are called with two arguments - the associated model instance and the
70971  * {@link Ext.data.Operation operation} object that was executed to load that instance. The Operation object is
70972  * useful when the instance could not be loaded.</p>
70973  *
70974  * <p><u>Generated setter function</u></p>
70975  *
70976  * <p>The second generated function sets the associated model instance - if only a single argument is passed to
70977  * the setter then the following two calls are identical:</p>
70978  *
70979 <pre><code>
70980 //this call
70981 product.setCategory(10);
70982
70983 //is equivalent to this call:
70984 product.set('category_id', 10);
70985 </code></pre>
70986  * <p>If we pass in a second argument, the model will be automatically saved and the second argument passed to
70987  * the owner model's {@link Ext.data.Model#save save} method:</p>
70988 <pre><code>
70989 product.setCategory(10, function(product, operation) {
70990     //the product has been saved
70991     alert(product.get('category_id')); //now alerts 10
70992 });
70993
70994 //alternative syntax:
70995 product.setCategory(10, {
70996     callback: function(product, operation), //a function that will always be called
70997     success : function(product, operation), //a function that will only be called if the load succeeded
70998     failure : function(product, operation), //a function that will only be called if the load did not succeed
70999     scope   : this //optionally pass in a scope object to execute the callbacks in
71000 })
71001 </code></pre>
71002 *
71003  * <p><u>Customisation</u></p>
71004  *
71005  * <p>Associations reflect on the models they are linking to automatically set up properties such as the
71006  * {@link #primaryKey} and {@link #foreignKey}. These can alternatively be specified:</p>
71007  *
71008 <pre><code>
71009 Ext.define('Product', {
71010     fields: [...],
71011
71012     associations: [
71013         {type: 'belongsTo', model: 'Category', primaryKey: 'unique_id', foreignKey: 'cat_id'}
71014     ]
71015 });
71016  </code></pre>
71017  *
71018  * <p>Here we replaced the default primary key (defaults to 'id') and foreign key (calculated as 'category_id')
71019  * with our own settings. Usually this will not be needed.</p>
71020  */
71021 Ext.define('Ext.data.BelongsToAssociation', {
71022     extend: 'Ext.data.Association',
71023
71024     alias: 'association.belongsto',
71025
71026     /**
71027      * @cfg {String} foreignKey The name of the foreign key on the owner model that links it to the associated
71028      * model. Defaults to the lowercased name of the associated model plus "_id", e.g. an association with a
71029      * model called Product would set up a product_id foreign key.
71030      * <pre><code>
71031 Ext.define('Order', {
71032     extend: 'Ext.data.Model',
71033     fields: ['id', 'date'],
71034     hasMany: 'Product'
71035 });
71036
71037 Ext.define('Product', {
71038     extend: 'Ext.data.Model',
71039     fields: ['id', 'name', 'order_id'], // refers to the id of the order that this product belongs to
71040     belongsTo: 'Group'
71041 });
71042 var product = new Product({
71043     id: 1,
71044     name: 'Product 1',
71045     order_id: 22
71046 }, 1);
71047 product.getOrder(); // Will make a call to the server asking for order_id 22
71048
71049      * </code></pre>
71050      */
71051
71052     /**
71053      * @cfg {String} getterName The name of the getter function that will be added to the local model's prototype.
71054      * Defaults to 'get' + the name of the foreign model, e.g. getCategory
71055      */
71056
71057     /**
71058      * @cfg {String} setterName The name of the setter function that will be added to the local model's prototype.
71059      * Defaults to 'set' + the name of the foreign model, e.g. setCategory
71060      */
71061     
71062     /**
71063      * @cfg {String} type The type configuration can be used when creating associations using a configuration object.
71064      * Use 'belongsTo' to create a HasManyAssocation
71065      * <pre><code>
71066 associations: [{
71067     type: 'belongsTo',
71068     model: 'User'
71069 }]
71070      * </code></pre>
71071      */
71072
71073     constructor: function(config) {
71074         this.callParent(arguments);
71075
71076         var me             = this,
71077             ownerProto     = me.ownerModel.prototype,
71078             associatedName = me.associatedName,
71079             getterName     = me.getterName || 'get' + associatedName,
71080             setterName     = me.setterName || 'set' + associatedName;
71081
71082         Ext.applyIf(me, {
71083             name        : associatedName,
71084             foreignKey  : associatedName.toLowerCase() + "_id",
71085             instanceName: associatedName + 'BelongsToInstance',
71086             associationKey: associatedName.toLowerCase()
71087         });
71088
71089         ownerProto[getterName] = me.createGetter();
71090         ownerProto[setterName] = me.createSetter();
71091     },
71092
71093     /**
71094      * @private
71095      * Returns a setter function to be placed on the owner model's prototype
71096      * @return {Function} The setter function
71097      */
71098     createSetter: function() {
71099         var me              = this,
71100             ownerModel      = me.ownerModel,
71101             associatedModel = me.associatedModel,
71102             foreignKey      = me.foreignKey,
71103             primaryKey      = me.primaryKey;
71104
71105         //'this' refers to the Model instance inside this function
71106         return function(value, options, scope) {
71107             this.set(foreignKey, value);
71108
71109             if (typeof options == 'function') {
71110                 options = {
71111                     callback: options,
71112                     scope: scope || this
71113                 };
71114             }
71115
71116             if (Ext.isObject(options)) {
71117                 return this.save(options);
71118             }
71119         };
71120     },
71121
71122     /**
71123      * @private
71124      * Returns a getter function to be placed on the owner model's prototype. We cache the loaded instance
71125      * the first time it is loaded so that subsequent calls to the getter always receive the same reference.
71126      * @return {Function} The getter function
71127      */
71128     createGetter: function() {
71129         var me              = this,
71130             ownerModel      = me.ownerModel,
71131             associatedName  = me.associatedName,
71132             associatedModel = me.associatedModel,
71133             foreignKey      = me.foreignKey,
71134             primaryKey      = me.primaryKey,
71135             instanceName    = me.instanceName;
71136
71137         //'this' refers to the Model instance inside this function
71138         return function(options, scope) {
71139             options = options || {};
71140
71141             var foreignKeyId = this.get(foreignKey),
71142                 instance, callbackFn;
71143
71144             if (this[instanceName] === undefined) {
71145                 instance = Ext.ModelManager.create({}, associatedName);
71146                 instance.set(primaryKey, foreignKeyId);
71147
71148                 if (typeof options == 'function') {
71149                     options = {
71150                         callback: options,
71151                         scope: scope || this
71152                     };
71153                 }
71154
71155                 associatedModel.load(foreignKeyId, options);
71156             } else {
71157                 instance = this[instanceName];
71158
71159                 //TODO: We're duplicating the callback invokation code that the instance.load() call above
71160                 //makes here - ought to be able to normalize this - perhaps by caching at the Model.load layer
71161                 //instead of the association layer.
71162                 if (typeof options == 'function') {
71163                     options.call(scope || this, instance);
71164                 }
71165
71166                 if (options.success) {
71167                     options.success.call(scope || this, instance);
71168                 }
71169
71170                 if (options.callback) {
71171                     options.callback.call(scope || this, instance);
71172                 }
71173
71174                 return instance;
71175             }
71176         };
71177     },
71178
71179     /**
71180      * Read associated data
71181      * @private
71182      * @param {Ext.data.Model} record The record we're writing to
71183      * @param {Ext.data.reader.Reader} reader The reader for the associated model
71184      * @param {Object} associationData The raw associated data
71185      */
71186     read: function(record, reader, associationData){
71187         record[this.instanceName] = reader.read([associationData]).records[0];
71188     }
71189 });
71190
71191 /**
71192  * @class Ext.data.BufferStore
71193  * @extends Ext.data.Store
71194  * @ignore
71195  */
71196 Ext.define('Ext.data.BufferStore', {
71197     extend: 'Ext.data.Store',
71198     alias: 'store.buffer',
71199     sortOnLoad: false,
71200     filterOnLoad: false,
71201     
71202     constructor: function() {
71203         Ext.Error.raise('The BufferStore class has been deprecated. Instead, specify the buffered config option on Ext.data.Store');
71204     }
71205 });
71206 /**
71207  * @class Ext.direct.Manager
71208  * <p><b><u>Overview</u></b></p>
71209  *
71210  * <p>Ext.Direct aims to streamline communication between the client and server
71211  * by providing a single interface that reduces the amount of common code
71212  * typically required to validate data and handle returned data packets
71213  * (reading data, error conditions, etc).</p>
71214  *
71215  * <p>The Ext.direct namespace includes several classes for a closer integration
71216  * with the server-side. The Ext.data namespace also includes classes for working
71217  * with Ext.data.Stores which are backed by data from an Ext.Direct method.</p>
71218  *
71219  * <p><b><u>Specification</u></b></p>
71220  *
71221  * <p>For additional information consult the
71222  * <a href="http://sencha.com/products/extjs/extdirect">Ext.Direct Specification</a>.</p>
71223  *
71224  * <p><b><u>Providers</u></b></p>
71225  *
71226  * <p>Ext.Direct uses a provider architecture, where one or more providers are
71227  * used to transport data to and from the server. There are several providers
71228  * that exist in the core at the moment:</p><div class="mdetail-params"><ul>
71229  *
71230  * <li>{@link Ext.direct.JsonProvider JsonProvider} for simple JSON operations</li>
71231  * <li>{@link Ext.direct.PollingProvider PollingProvider} for repeated requests</li>
71232  * <li>{@link Ext.direct.RemotingProvider RemotingProvider} exposes server side
71233  * on the client.</li>
71234  * </ul></div>
71235  *
71236  * <p>A provider does not need to be invoked directly, providers are added via
71237  * {@link Ext.direct.Manager}.{@link Ext.direct.Manager#add add}.</p>
71238  *
71239  * <p><b><u>Router</u></b></p>
71240  *
71241  * <p>Ext.Direct utilizes a "router" on the server to direct requests from the client
71242  * to the appropriate server-side method. Because the Ext.Direct API is completely
71243  * platform-agnostic, you could completely swap out a Java based server solution
71244  * and replace it with one that uses C# without changing the client side JavaScript
71245  * at all.</p>
71246  *
71247  * <p><b><u>Server side events</u></b></p>
71248  *
71249  * <p>Custom events from the server may be handled by the client by adding
71250  * listeners, for example:</p>
71251  * <pre><code>
71252 {"type":"event","name":"message","data":"Successfully polled at: 11:19:30 am"}
71253
71254 // add a handler for a 'message' event sent by the server
71255 Ext.direct.Manager.on('message', function(e){
71256     out.append(String.format('&lt;p>&lt;i>{0}&lt;/i>&lt;/p>', e.data));
71257             out.el.scrollTo('t', 100000, true);
71258 });
71259  * </code></pre>
71260  * @singleton
71261  */
71262
71263 Ext.define('Ext.direct.Manager', {
71264     
71265     /* Begin Definitions */
71266     singleton: true,
71267    
71268     mixins: {
71269         observable: 'Ext.util.Observable'
71270     },
71271     
71272     requires: ['Ext.util.MixedCollection'],
71273     
71274     statics: {
71275         exceptions: {
71276             TRANSPORT: 'xhr',
71277             PARSE: 'parse',
71278             LOGIN: 'login',
71279             SERVER: 'exception'
71280         }
71281     },
71282     
71283     /* End Definitions */
71284    
71285     constructor: function(){
71286         var me = this;
71287        
71288         me.addEvents(
71289             /**
71290              * @event event
71291              * Fires after an event.
71292              * @param {event} e The Ext.direct.Event type that occurred.
71293              * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider}.
71294              */
71295             'event',
71296             /**
71297              * @event exception
71298              * Fires after an event exception.
71299              * @param {event} e The Ext.direct.Event type that occurred.
71300              */
71301             'exception'
71302         );
71303         me.transactions = Ext.create('Ext.util.MixedCollection');
71304         me.providers = Ext.create('Ext.util.MixedCollection');
71305         
71306         me.mixins.observable.constructor.call(me);
71307     },
71308     
71309     /**
71310      * Adds an Ext.Direct Provider and creates the proxy or stub methods to execute server-side methods.
71311      * If the provider is not already connected, it will auto-connect.
71312      * <pre><code>
71313 var pollProv = new Ext.direct.PollingProvider({
71314     url: 'php/poll2.php'
71315 });
71316
71317 Ext.direct.Manager.addProvider({
71318     "type":"remoting",       // create a {@link Ext.direct.RemotingProvider}
71319     "url":"php\/router.php", // url to connect to the Ext.Direct server-side router.
71320     "actions":{              // each property within the actions object represents a Class
71321         "TestAction":[       // array of methods within each server side Class
71322         {
71323             "name":"doEcho", // name of method
71324             "len":1
71325         },{
71326             "name":"multiply",
71327             "len":1
71328         },{
71329             "name":"doForm",
71330             "formHandler":true, // handle form on server with Ext.Direct.Transaction
71331             "len":1
71332         }]
71333     },
71334     "namespace":"myApplication",// namespace to create the Remoting Provider in
71335 },{
71336     type: 'polling', // create a {@link Ext.direct.PollingProvider}
71337     url:  'php/poll.php'
71338 }, pollProv); // reference to previously created instance
71339      * </code></pre>
71340      * @param {Object/Array} provider Accepts either an Array of Provider descriptions (an instance
71341      * or config object for a Provider) or any number of Provider descriptions as arguments.  Each
71342      * Provider description instructs Ext.Direct how to create client-side stub methods.
71343      */
71344     addProvider : function(provider){
71345         var me = this,
71346             args = arguments,
71347             i = 0,
71348             len;
71349             
71350         if (args.length > 1) {
71351             for (len = args.length; i < len; ++i) {
71352                 me.addProvider(args[i]);
71353             }
71354             return;
71355         }
71356
71357         // if provider has not already been instantiated
71358         if (!provider.isProvider) {
71359             provider = Ext.create('direct.' + provider.type + 'provider', provider);
71360         }
71361         me.providers.add(provider);
71362         provider.on('data', me.onProviderData, me);
71363
71364
71365         if (!provider.isConnected()) {
71366             provider.connect();
71367         }
71368
71369         return provider;
71370     },
71371     
71372     /**
71373      * Retrieve a {@link Ext.direct.Provider provider} by the
71374      * <b><tt>{@link Ext.direct.Provider#id id}</tt></b> specified when the provider is
71375      * {@link #addProvider added}.
71376      * @param {String/Ext.data.Provider} id The id of the provider, or the provider instance.
71377      */
71378     getProvider : function(id){
71379         return id.isProvider ? id : this.providers.get(id);
71380     },
71381     
71382     /**
71383      * Removes the provider.
71384      * @param {String/Ext.direct.Provider} provider The provider instance or the id of the provider.
71385      * @return {Ext.direct.Provider} The provider, null if not found.
71386      */
71387     removeProvider : function(provider){
71388         var me = this,
71389             providers = me.providers,
71390             provider = provider.isProvider ? provider : providers.get(provider);
71391             
71392         if (provider) {
71393             provider.un('data', me.onProviderData, me);
71394             providers.remove(provider);
71395             return provider;
71396         }
71397         return null;
71398     },
71399     
71400     /**
71401      * Add a transaction to the manager.
71402      * @private
71403      * @param {Ext.direct.Transaction} transaction The transaction to add
71404      * @return {Ext.direct.Transaction} transaction
71405      */
71406     addTransaction: function(transaction){
71407         this.transactions.add(transaction);
71408         return transaction;
71409     },
71410
71411     /**
71412      * Remove a transaction from the manager.
71413      * @private
71414      * @param {String/Ext.direct.Transaction} transaction The transaction/id of transaction to remove
71415      * @return {Ext.direct.Transaction} transaction
71416      */
71417     removeTransaction: function(transaction){
71418         transaction = this.getTransaction(transaction);
71419         this.transactions.remove(transaction);
71420         return transaction;
71421     },
71422
71423     /**
71424      * Gets a transaction
71425      * @private
71426      * @param {String/Ext.direct.Transaction} transaction The transaction/id of transaction to get
71427      * @return {Ext.direct.Transaction}
71428      */
71429     getTransaction: function(transaction){
71430         return transaction.isTransaction ? transaction : this.transactions.get(transaction);
71431     },
71432     
71433     onProviderData : function(provider, event){
71434         var me = this,
71435             i = 0,
71436             len;
71437             
71438         if (Ext.isArray(event)) {
71439             for (len = event.length; i < len; ++i) {
71440                 me.onProviderData(provider, event[i]);
71441             }
71442             return;
71443         }
71444         if (event.name && event.name != 'event' && event.name != 'exception') {
71445             me.fireEvent(event.name, event);
71446         } else if (event.type == 'exception') {
71447             me.fireEvent('exception', event);
71448         }
71449         me.fireEvent('event', event, provider);
71450     }
71451 }, function(){
71452     // Backwards compatibility
71453     Ext.Direct = Ext.direct.Manager;
71454 });
71455
71456 /**
71457  * @class Ext.data.proxy.Direct
71458  * @extends Ext.data.proxy.Server
71459  * 
71460  * This class is used to send requests to the server using {@link Ext.direct}. When a request is made,
71461  * the transport mechanism is handed off to the appropriate {@link Ext.direct.RemotingProvider Provider}
71462  * to complete the call.
71463  * 
71464  * ## Specifying the function
71465  * This proxy expects a Direct remoting method to be passed in order to be able to complete requests.
71466  * This can be done by specifying the {@link #directFn} configuration. This will use the same direct
71467  * method for all requests. Alternatively, you can provide an {@link #api} configuration. This
71468  * allows you to specify a different remoting method for each CRUD action.
71469  * 
71470  * ## Paramaters
71471  * This proxy provides options to help configure which parameters will be sent to the server.
71472  * By specifying the {@link #paramsAsHash} option, it will send an object literal containing each
71473  * of the passed parameters. The {@link #paramOrder} option can be used to specify the order in which
71474  * the remoting method parameters are passed.
71475  * 
71476  * ## Example Usage
71477  * 
71478  *     Ext.define('User', {
71479  *         extend: 'Ext.data.Model',
71480  *         fields: ['firstName', 'lastName'],
71481  *         proxy: {
71482  *             type: 'direct',
71483  *             directFn: MyApp.getUsers,
71484  *             paramOrder: 'id' // Tells the proxy to pass the id as the first parameter to the remoting method.
71485  *         }
71486  *     });
71487  *     User.load(1);
71488  */
71489 Ext.define('Ext.data.proxy.Direct', {
71490     /* Begin Definitions */
71491     
71492     extend: 'Ext.data.proxy.Server',
71493     alternateClassName: 'Ext.data.DirectProxy',
71494     
71495     alias: 'proxy.direct',
71496     
71497     requires: ['Ext.direct.Manager'],
71498     
71499     /* End Definitions */
71500    
71501    /**
71502      * @cfg {Array/String} paramOrder Defaults to <tt>undefined</tt>. A list of params to be executed
71503      * server side.  Specify the params in the order in which they must be executed on the server-side
71504      * as either (1) an Array of String values, or (2) a String of params delimited by either whitespace,
71505      * comma, or pipe. For example,
71506      * any of the following would be acceptable:<pre><code>
71507 paramOrder: ['param1','param2','param3']
71508 paramOrder: 'param1 param2 param3'
71509 paramOrder: 'param1,param2,param3'
71510 paramOrder: 'param1|param2|param'
71511      </code></pre>
71512      */
71513     paramOrder: undefined,
71514
71515     /**
71516      * @cfg {Boolean} paramsAsHash
71517      * Send parameters as a collection of named arguments (defaults to <tt>true</tt>). Providing a
71518      * <tt>{@link #paramOrder}</tt> nullifies this configuration.
71519      */
71520     paramsAsHash: true,
71521
71522     /**
71523      * @cfg {Function} directFn
71524      * Function to call when executing a request.  directFn is a simple alternative to defining the api configuration-parameter
71525      * for Store's which will not implement a full CRUD api.
71526      */
71527     directFn : undefined,
71528     
71529     /**
71530      * @cfg {Object} api The same as {@link Ext.data.proxy.Server#api}, however instead of providing urls, you should provide a direct
71531      * function call.
71532      */
71533     
71534     /**
71535      * @cfg {Object} extraParams Extra parameters that will be included on every read request. Individual requests with params
71536      * of the same name will override these params when they are in conflict.
71537      */
71538     
71539     // private
71540     paramOrderRe: /[\s,|]/,
71541     
71542     constructor: function(config){
71543         var me = this;
71544         
71545         Ext.apply(me, config);
71546         if (Ext.isString(me.paramOrder)) {
71547             me.paramOrder = me.paramOrder.split(me.paramOrderRe);
71548         }
71549         me.callParent(arguments);
71550     },
71551     
71552     doRequest: function(operation, callback, scope) {
71553         var me = this,
71554             writer = me.getWriter(),
71555             request = me.buildRequest(operation, callback, scope),
71556             fn = me.api[request.action]  || me.directFn,
71557             args = [],
71558             params = request.params,
71559             paramOrder = me.paramOrder,
71560             method,
71561             i = 0,
71562             len;
71563             
71564         if (!fn) {
71565             Ext.Error.raise('No direct function specified for this proxy');
71566         }
71567             
71568         if (operation.allowWrite()) {
71569             request = writer.write(request);
71570         }
71571         
71572         if (operation.action == 'read') {
71573             // We need to pass params
71574             method = fn.directCfg.method;
71575             
71576             if (method.ordered) {
71577                 if (method.len > 0) {
71578                     if (paramOrder) {
71579                         for (len = paramOrder.length; i < len; ++i) {
71580                             args.push(params[paramOrder[i]]);
71581                         }
71582                     } else if (me.paramsAsHash) {
71583                         args.push(params);
71584                     }
71585                 }
71586             } else {
71587                 args.push(params);
71588             }
71589         } else {
71590             args.push(request.jsonData);
71591         }
71592         
71593         Ext.apply(request, {
71594             args: args,
71595             directFn: fn
71596         });
71597         args.push(me.createRequestCallback(request, operation, callback, scope), me);
71598         fn.apply(window, args);
71599     },
71600     
71601     /*
71602      * Inherit docs. We don't apply any encoding here because
71603      * all of the direct requests go out as jsonData
71604      */
71605     applyEncoding: function(value){
71606         return value;
71607     },
71608     
71609     createRequestCallback: function(request, operation, callback, scope){
71610         var me = this;
71611         
71612         return function(data, event){
71613             me.processResponse(event.status, operation, request, event, callback, scope);
71614         };
71615     },
71616     
71617     // inherit docs
71618     extractResponseData: function(response){
71619         return Ext.isDefined(response.result) ? response.result : response.data;
71620     },
71621     
71622     // inherit docs
71623     setException: function(operation, response) {
71624         operation.setException(response.message);
71625     },
71626     
71627     // inherit docs
71628     buildUrl: function(){
71629         return '';
71630     }
71631 });
71632
71633 /**
71634  * @class Ext.data.DirectStore
71635  * @extends Ext.data.Store
71636  * <p>Small helper class to create an {@link Ext.data.Store} configured with an
71637  * {@link Ext.data.proxy.Direct} and {@link Ext.data.reader.Json} to make interacting
71638  * with an {@link Ext.Direct} Server-side {@link Ext.direct.Provider Provider} easier.
71639  * To create a different proxy/reader combination create a basic {@link Ext.data.Store}
71640  * configured as needed.</p>
71641  *
71642  * <p><b>*Note:</b> Although they are not listed, this class inherits all of the config options of:</p>
71643  * <div><ul class="mdetail-params">
71644  * <li><b>{@link Ext.data.Store Store}</b></li>
71645  * <div class="sub-desc"><ul class="mdetail-params">
71646  *
71647  * </ul></div>
71648  * <li><b>{@link Ext.data.reader.Json JsonReader}</b></li>
71649  * <div class="sub-desc"><ul class="mdetail-params">
71650  * <li><tt><b>{@link Ext.data.reader.Json#root root}</b></tt></li>
71651  * <li><tt><b>{@link Ext.data.reader.Json#idProperty idProperty}</b></tt></li>
71652  * <li><tt><b>{@link Ext.data.reader.Json#totalProperty totalProperty}</b></tt></li>
71653  * </ul></div>
71654  *
71655  * <li><b>{@link Ext.data.proxy.Direct DirectProxy}</b></li>
71656  * <div class="sub-desc"><ul class="mdetail-params">
71657  * <li><tt><b>{@link Ext.data.proxy.Direct#directFn directFn}</b></tt></li>
71658  * <li><tt><b>{@link Ext.data.proxy.Direct#paramOrder paramOrder}</b></tt></li>
71659  * <li><tt><b>{@link Ext.data.proxy.Direct#paramsAsHash paramsAsHash}</b></tt></li>
71660  * </ul></div>
71661  * </ul></div>
71662  *
71663  * @constructor
71664  * @param {Object} config
71665  */
71666
71667 Ext.define('Ext.data.DirectStore', {
71668     /* Begin Definitions */
71669     
71670     extend: 'Ext.data.Store',
71671     
71672     alias: 'store.direct',
71673     
71674     requires: ['Ext.data.proxy.Direct'],
71675    
71676     /* End Definitions */
71677    
71678    constructor : function(config){
71679         config = Ext.apply({}, config);
71680         if (!config.proxy) {
71681             var proxy = {
71682                 type: 'direct',
71683                 reader: {
71684                     type: 'json'
71685                 }
71686             };
71687             Ext.copyTo(proxy, config, 'paramOrder,paramsAsHash,directFn,api,simpleSortMode');
71688             Ext.copyTo(proxy.reader, config, 'totalProperty,root,idProperty');
71689             config.proxy = proxy;
71690         }
71691         this.callParent([config]);
71692     }    
71693 });
71694
71695 /**
71696  * @class Ext.util.Inflector
71697  * @extends Object
71698  * <p>General purpose inflector class that {@link #pluralize pluralizes}, {@link #singularize singularizes} and 
71699  * {@link #ordinalize ordinalizes} words. Sample usage:</p>
71700  * 
71701 <pre><code>
71702 //turning singular words into plurals
71703 Ext.util.Inflector.pluralize('word'); //'words'
71704 Ext.util.Inflector.pluralize('person'); //'people'
71705 Ext.util.Inflector.pluralize('sheep'); //'sheep'
71706
71707 //turning plurals into singulars
71708 Ext.util.Inflector.singularize('words'); //'word'
71709 Ext.util.Inflector.singularize('people'); //'person'
71710 Ext.util.Inflector.singularize('sheep'); //'sheep'
71711
71712 //ordinalizing numbers
71713 Ext.util.Inflector.ordinalize(11); //"11th"
71714 Ext.util.Inflector.ordinalize(21); //"21th"
71715 Ext.util.Inflector.ordinalize(1043); //"1043rd"
71716 </code></pre>
71717  * 
71718  * <p><u>Customization</u></p>
71719  * 
71720  * <p>The Inflector comes with a default set of US English pluralization rules. These can be augmented with additional
71721  * rules if the default rules do not meet your application's requirements, or swapped out entirely for other languages.
71722  * Here is how we might add a rule that pluralizes "ox" to "oxen":</p>
71723  * 
71724 <pre><code>
71725 Ext.util.Inflector.plural(/^(ox)$/i, "$1en");
71726 </code></pre>
71727  * 
71728  * <p>Each rule consists of two items - a regular expression that matches one or more rules, and a replacement string.
71729  * In this case, the regular expression will only match the string "ox", and will replace that match with "oxen". 
71730  * Here's how we could add the inverse rule:</p>
71731  * 
71732 <pre><code>
71733 Ext.util.Inflector.singular(/^(ox)en$/i, "$1");
71734 </code></pre>
71735  * 
71736  * <p>Note that the ox/oxen rules are present by default.</p>
71737  * 
71738  * @singleton
71739  */
71740
71741 Ext.define('Ext.util.Inflector', {
71742
71743     /* Begin Definitions */
71744
71745     singleton: true,
71746
71747     /* End Definitions */
71748
71749     /**
71750      * @private
71751      * The registered plural tuples. Each item in the array should contain two items - the first must be a regular
71752      * expression that matchers the singular form of a word, the second must be a String that replaces the matched
71753      * part of the regular expression. This is managed by the {@link #plural} method.
71754      * @property plurals
71755      * @type Array
71756      */
71757     plurals: [
71758         [(/(quiz)$/i),                "$1zes"  ],
71759         [(/^(ox)$/i),                 "$1en"   ],
71760         [(/([m|l])ouse$/i),           "$1ice"  ],
71761         [(/(matr|vert|ind)ix|ex$/i),  "$1ices" ],
71762         [(/(x|ch|ss|sh)$/i),          "$1es"   ],
71763         [(/([^aeiouy]|qu)y$/i),       "$1ies"  ],
71764         [(/(hive)$/i),                "$1s"    ],
71765         [(/(?:([^f])fe|([lr])f)$/i),  "$1$2ves"],
71766         [(/sis$/i),                   "ses"    ],
71767         [(/([ti])um$/i),              "$1a"    ],
71768         [(/(buffal|tomat|potat)o$/i), "$1oes"  ],
71769         [(/(bu)s$/i),                 "$1ses"  ],
71770         [(/(alias|status|sex)$/i),    "$1es"   ],
71771         [(/(octop|vir)us$/i),         "$1i"    ],
71772         [(/(ax|test)is$/i),           "$1es"   ],
71773         [(/^person$/),                "people" ],
71774         [(/^man$/),                   "men"    ],
71775         [(/^(child)$/),               "$1ren"  ],
71776         [(/s$/i),                     "s"      ],
71777         [(/$/),                       "s"      ]
71778     ],
71779     
71780     /**
71781      * @private
71782      * The set of registered singular matchers. Each item in the array should contain two items - the first must be a 
71783      * regular expression that matches the plural form of a word, the second must be a String that replaces the 
71784      * matched part of the regular expression. This is managed by the {@link #singular} method.
71785      * @property singulars
71786      * @type Array
71787      */
71788     singulars: [
71789       [(/(quiz)zes$/i),                                                    "$1"     ],
71790       [(/(matr)ices$/i),                                                   "$1ix"   ],
71791       [(/(vert|ind)ices$/i),                                               "$1ex"   ],
71792       [(/^(ox)en/i),                                                       "$1"     ],
71793       [(/(alias|status)es$/i),                                             "$1"     ],
71794       [(/(octop|vir)i$/i),                                                 "$1us"   ],
71795       [(/(cris|ax|test)es$/i),                                             "$1is"   ],
71796       [(/(shoe)s$/i),                                                      "$1"     ],
71797       [(/(o)es$/i),                                                        "$1"     ],
71798       [(/(bus)es$/i),                                                      "$1"     ],
71799       [(/([m|l])ice$/i),                                                   "$1ouse" ],
71800       [(/(x|ch|ss|sh)es$/i),                                               "$1"     ],
71801       [(/(m)ovies$/i),                                                     "$1ovie" ],
71802       [(/(s)eries$/i),                                                     "$1eries"],
71803       [(/([^aeiouy]|qu)ies$/i),                                            "$1y"    ],
71804       [(/([lr])ves$/i),                                                    "$1f"    ],
71805       [(/(tive)s$/i),                                                      "$1"     ],
71806       [(/(hive)s$/i),                                                      "$1"     ],
71807       [(/([^f])ves$/i),                                                    "$1fe"   ],
71808       [(/(^analy)ses$/i),                                                  "$1sis"  ],
71809       [(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i), "$1$2sis"],
71810       [(/([ti])a$/i),                                                      "$1um"   ],
71811       [(/(n)ews$/i),                                                       "$1ews"  ],
71812       [(/people$/i),                                                       "person" ],
71813       [(/s$/i),                                                            ""       ]
71814     ],
71815     
71816     /**
71817      * @private
71818      * The registered uncountable words
71819      * @property uncountable
71820      * @type Array
71821      */
71822      uncountable: [
71823         "sheep",
71824         "fish",
71825         "series",
71826         "species",
71827         "money",
71828         "rice",
71829         "information",
71830         "equipment",
71831         "grass",
71832         "mud",
71833         "offspring",
71834         "deer",
71835         "means"
71836     ],
71837     
71838     /**
71839      * Adds a new singularization rule to the Inflector. See the intro docs for more information
71840      * @param {RegExp} matcher The matcher regex
71841      * @param {String} replacer The replacement string, which can reference matches from the matcher argument
71842      */
71843     singular: function(matcher, replacer) {
71844         this.singulars.unshift([matcher, replacer]);
71845     },
71846     
71847     /**
71848      * Adds a new pluralization rule to the Inflector. See the intro docs for more information
71849      * @param {RegExp} matcher The matcher regex
71850      * @param {String} replacer The replacement string, which can reference matches from the matcher argument
71851      */
71852     plural: function(matcher, replacer) {
71853         this.plurals.unshift([matcher, replacer]);
71854     },
71855     
71856     /**
71857      * Removes all registered singularization rules
71858      */
71859     clearSingulars: function() {
71860         this.singulars = [];
71861     },
71862     
71863     /**
71864      * Removes all registered pluralization rules
71865      */
71866     clearPlurals: function() {
71867         this.plurals = [];
71868     },
71869     
71870     /**
71871      * Returns true if the given word is transnumeral (the word is its own singular and plural form - e.g. sheep, fish)
71872      * @param {String} word The word to test
71873      * @return {Boolean} True if the word is transnumeral
71874      */
71875     isTransnumeral: function(word) {
71876         return Ext.Array.indexOf(this.uncountable, word) != -1;
71877     },
71878
71879     /**
71880      * Returns the pluralized form of a word (e.g. Ext.util.Inflector.pluralize('word') returns 'words')
71881      * @param {String} word The word to pluralize
71882      * @return {String} The pluralized form of the word
71883      */
71884     pluralize: function(word) {
71885         if (this.isTransnumeral(word)) {
71886             return word;
71887         }
71888
71889         var plurals = this.plurals,
71890             length  = plurals.length,
71891             tuple, regex, i;
71892         
71893         for (i = 0; i < length; i++) {
71894             tuple = plurals[i];
71895             regex = tuple[0];
71896             
71897             if (regex == word || (regex.test && regex.test(word))) {
71898                 return word.replace(regex, tuple[1]);
71899             }
71900         }
71901         
71902         return word;
71903     },
71904     
71905     /**
71906      * Returns the singularized form of a word (e.g. Ext.util.Inflector.singularize('words') returns 'word')
71907      * @param {String} word The word to singularize
71908      * @return {String} The singularized form of the word
71909      */
71910     singularize: function(word) {
71911         if (this.isTransnumeral(word)) {
71912             return word;
71913         }
71914
71915         var singulars = this.singulars,
71916             length    = singulars.length,
71917             tuple, regex, i;
71918         
71919         for (i = 0; i < length; i++) {
71920             tuple = singulars[i];
71921             regex = tuple[0];
71922             
71923             if (regex == word || (regex.test && regex.test(word))) {
71924                 return word.replace(regex, tuple[1]);
71925             }
71926         }
71927         
71928         return word;
71929     },
71930     
71931     /**
71932      * Returns the correct {@link Ext.data.Model Model} name for a given string. Mostly used internally by the data 
71933      * package
71934      * @param {String} word The word to classify
71935      * @return {String} The classified version of the word
71936      */
71937     classify: function(word) {
71938         return Ext.String.capitalize(this.singularize(word));
71939     },
71940     
71941     /**
71942      * Ordinalizes a given number by adding a prefix such as 'st', 'nd', 'rd' or 'th' based on the last digit of the 
71943      * number. 21 -> 21st, 22 -> 22nd, 23 -> 23rd, 24 -> 24th etc
71944      * @param {Number} number The number to ordinalize
71945      * @return {String} The ordinalized number
71946      */
71947     ordinalize: function(number) {
71948         var parsed = parseInt(number, 10),
71949             mod10  = parsed % 10,
71950             mod100 = parsed % 100;
71951         
71952         //11 through 13 are a special case
71953         if (11 <= mod100 && mod100 <= 13) {
71954             return number + "th";
71955         } else {
71956             switch(mod10) {
71957                 case 1 : return number + "st";
71958                 case 2 : return number + "nd";
71959                 case 3 : return number + "rd";
71960                 default: return number + "th";
71961             }
71962         }
71963     }
71964 }, function() {
71965     //aside from the rules above, there are a number of words that have irregular pluralization so we add them here
71966     var irregulars = {
71967             alumnus: 'alumni',
71968             cactus : 'cacti',
71969             focus  : 'foci',
71970             nucleus: 'nuclei',
71971             radius: 'radii',
71972             stimulus: 'stimuli',
71973             ellipsis: 'ellipses',
71974             paralysis: 'paralyses',
71975             oasis: 'oases',
71976             appendix: 'appendices',
71977             index: 'indexes',
71978             beau: 'beaux',
71979             bureau: 'bureaux',
71980             tableau: 'tableaux',
71981             woman: 'women',
71982             child: 'children',
71983             man: 'men',
71984             corpus:     'corpora',
71985             criterion: 'criteria',
71986             curriculum: 'curricula',
71987             genus: 'genera',
71988             memorandum: 'memoranda',
71989             phenomenon: 'phenomena',
71990             foot: 'feet',
71991             goose: 'geese',
71992             tooth: 'teeth',
71993             antenna: 'antennae',
71994             formula: 'formulae',
71995             nebula: 'nebulae',
71996             vertebra: 'vertebrae',
71997             vita: 'vitae'
71998         },
71999         singular;
72000     
72001     for (singular in irregulars) {
72002         this.plural(singular, irregulars[singular]);
72003         this.singular(irregulars[singular], singular);
72004     }
72005 });
72006 /**
72007  * @author Ed Spencer
72008  * @class Ext.data.HasManyAssociation
72009  * @extends Ext.data.Association
72010  * 
72011  * <p>Represents a one-to-many relationship between two models. Usually created indirectly via a model definition:</p>
72012  * 
72013 <pre><code>
72014 Ext.define('Product', {
72015     extend: 'Ext.data.Model',
72016     fields: [
72017         {name: 'id',      type: 'int'},
72018         {name: 'user_id', type: 'int'},
72019         {name: 'name',    type: 'string'}
72020     ]
72021 });
72022
72023 Ext.define('User', {
72024     extend: 'Ext.data.Model',
72025     fields: [
72026         {name: 'id',   type: 'int'},
72027         {name: 'name', type: 'string'}
72028     ],
72029     // we can use the hasMany shortcut on the model to create a hasMany association
72030     hasMany: {model: 'Product', name: 'products'}
72031 });
72032 </pre></code>
72033
72034  * <p>Above we created Product and User models, and linked them by saying that a User hasMany Products. This gives
72035  * us a new function on every User instance, in this case the function is called 'products' because that is the name
72036  * we specified in the association configuration above.</p>
72037  * 
72038  * <p>This new function returns a specialized {@link Ext.data.Store Store} which is automatically filtered to load
72039  * only Products for the given model instance:</p>
72040  * 
72041 <pre><code>
72042 //first, we load up a User with id of 1
72043 var user = Ext.ModelManager.create({id: 1, name: 'Ed'}, 'User');
72044
72045 //the user.products function was created automatically by the association and returns a {@link Ext.data.Store Store}
72046 //the created store is automatically scoped to the set of Products for the User with id of 1
72047 var products = user.products();
72048
72049 //we still have all of the usual Store functions, for example it's easy to add a Product for this User
72050 products.add({
72051     name: 'Another Product'
72052 });
72053
72054 //saves the changes to the store - this automatically sets the new Product's user_id to 1 before saving
72055 products.sync();
72056 </code></pre>
72057  * 
72058  * <p>The new Store is only instantiated the first time you call products() to conserve memory and processing time,
72059  * though calling products() a second time returns the same store instance.</p>
72060  * 
72061  * <p><u>Custom filtering</u></p>
72062  * 
72063  * <p>The Store is automatically furnished with a filter - by default this filter tells the store to only return
72064  * records where the associated model's foreign key matches the owner model's primary key. For example, if a User
72065  * with ID = 100 hasMany Products, the filter loads only Products with user_id == 100.</p>
72066  * 
72067  * <p>Sometimes we want to filter by another field - for example in the case of a Twitter search application we may
72068  * have models for Search and Tweet:</p>
72069  * 
72070 <pre><code>
72071 Ext.define('Search', {
72072     extend: 'Ext.data.Model',
72073     fields: [
72074         'id', 'query'
72075     ],
72076
72077     hasMany: {
72078         model: 'Tweet',
72079         name : 'tweets',
72080         filterProperty: 'query'
72081     }
72082 });
72083
72084 Ext.define('Tweet', {
72085     extend: 'Ext.data.Model',
72086     fields: [
72087         'id', 'text', 'from_user'
72088     ]
72089 });
72090
72091 //returns a Store filtered by the filterProperty
72092 var store = new Search({query: 'Sencha Touch'}).tweets();
72093 </code></pre>
72094  * 
72095  * <p>The tweets association above is filtered by the query property by setting the {@link #filterProperty}, and is
72096  * equivalent to this:</p>
72097  * 
72098 <pre><code>
72099 var store = new Ext.data.Store({
72100     model: 'Tweet',
72101     filters: [
72102         {
72103             property: 'query',
72104             value   : 'Sencha Touch'
72105         }
72106     ]
72107 });
72108 </code></pre>
72109  */
72110 Ext.define('Ext.data.HasManyAssociation', {
72111     extend: 'Ext.data.Association',
72112     requires: ['Ext.util.Inflector'],
72113
72114     alias: 'association.hasmany',
72115
72116     /**
72117      * @cfg {String} foreignKey The name of the foreign key on the associated model that links it to the owner
72118      * model. Defaults to the lowercased name of the owner model plus "_id", e.g. an association with a where a
72119      * model called Group hasMany Users would create 'group_id' as the foreign key. When the remote store is loaded,
72120      * the store is automatically filtered so that only records with a matching foreign key are included in the 
72121      * resulting child store. This can be overridden by specifying the {@link #filterProperty}.
72122      * <pre><code>
72123 Ext.define('Group', {
72124     extend: 'Ext.data.Model',
72125     fields: ['id', 'name'],
72126     hasMany: 'User'
72127 });
72128
72129 Ext.define('User', {
72130     extend: 'Ext.data.Model',
72131     fields: ['id', 'name', 'group_id'], // refers to the id of the group that this user belongs to
72132     belongsTo: 'Group'
72133 });
72134      * </code></pre>
72135      */
72136     
72137     /**
72138      * @cfg {String} name The name of the function to create on the owner model to retrieve the child store.
72139      * If not specified, the pluralized name of the child model is used.
72140      * <pre><code>
72141 // This will create a users() method on any Group model instance
72142 Ext.define('Group', {
72143     extend: 'Ext.data.Model',
72144     fields: ['id', 'name'],
72145     hasMany: 'User'
72146 });
72147 var group = new Group();
72148 console.log(group.users());
72149
72150 // The method to retrieve the users will now be getUserList
72151 Ext.define('Group', {
72152     extend: 'Ext.data.Model',
72153     fields: ['id', 'name'],
72154     hasMany: {model: 'User', name: 'getUserList'}
72155 });
72156 var group = new Group();
72157 console.log(group.getUserList());
72158      * </code></pre>
72159      */
72160     
72161     /**
72162      * @cfg {Object} storeConfig Optional configuration object that will be passed to the generated Store. Defaults to 
72163      * undefined.
72164      */
72165     
72166     /**
72167      * @cfg {String} filterProperty Optionally overrides the default filter that is set up on the associated Store. If
72168      * this is not set, a filter is automatically created which filters the association based on the configured 
72169      * {@link #foreignKey}. See intro docs for more details. Defaults to undefined
72170      */
72171     
72172     /**
72173      * @cfg {Boolean} autoLoad True to automatically load the related store from a remote source when instantiated.
72174      * Defaults to <tt>false</tt>.
72175      */
72176     
72177     /**
72178      * @cfg {String} type The type configuration can be used when creating associations using a configuration object.
72179      * Use 'hasMany' to create a HasManyAssocation
72180      * <pre><code>
72181 associations: [{
72182     type: 'hasMany',
72183     model: 'User'
72184 }]
72185      * </code></pre>
72186      */
72187     
72188     constructor: function(config) {
72189         var me = this,
72190             ownerProto,
72191             name;
72192             
72193         me.callParent(arguments);
72194         
72195         me.name = me.name || Ext.util.Inflector.pluralize(me.associatedName.toLowerCase());
72196         
72197         ownerProto = me.ownerModel.prototype;
72198         name = me.name;
72199         
72200         Ext.applyIf(me, {
72201             storeName : name + "Store",
72202             foreignKey: me.ownerName.toLowerCase() + "_id"
72203         });
72204         
72205         ownerProto[name] = me.createStore();
72206     },
72207     
72208     /**
72209      * @private
72210      * Creates a function that returns an Ext.data.Store which is configured to load a set of data filtered
72211      * by the owner model's primary key - e.g. in a hasMany association where Group hasMany Users, this function
72212      * returns a Store configured to return the filtered set of a single Group's Users.
72213      * @return {Function} The store-generating function
72214      */
72215     createStore: function() {
72216         var that            = this,
72217             associatedModel = that.associatedModel,
72218             storeName       = that.storeName,
72219             foreignKey      = that.foreignKey,
72220             primaryKey      = that.primaryKey,
72221             filterProperty  = that.filterProperty,
72222             autoLoad        = that.autoLoad,
72223             storeConfig     = that.storeConfig || {};
72224         
72225         return function() {
72226             var me = this,
72227                 config, filter,
72228                 modelDefaults = {};
72229                 
72230             if (me[storeName] === undefined) {
72231                 if (filterProperty) {
72232                     filter = {
72233                         property  : filterProperty,
72234                         value     : me.get(filterProperty),
72235                         exactMatch: true
72236                     };
72237                 } else {
72238                     filter = {
72239                         property  : foreignKey,
72240                         value     : me.get(primaryKey),
72241                         exactMatch: true
72242                     };
72243                 }
72244                 
72245                 modelDefaults[foreignKey] = me.get(primaryKey);
72246                 
72247                 config = Ext.apply({}, storeConfig, {
72248                     model        : associatedModel,
72249                     filters      : [filter],
72250                     remoteFilter : false,
72251                     modelDefaults: modelDefaults
72252                 });
72253                 
72254                 me[storeName] = Ext.create('Ext.data.Store', config);
72255                 if (autoLoad) {
72256                     me[storeName].load();
72257                 }
72258             }
72259             
72260             return me[storeName];
72261         };
72262     },
72263     
72264     /**
72265      * Read associated data
72266      * @private
72267      * @param {Ext.data.Model} record The record we're writing to
72268      * @param {Ext.data.reader.Reader} reader The reader for the associated model
72269      * @param {Object} associationData The raw associated data
72270      */
72271     read: function(record, reader, associationData){
72272         var store = record[this.name](),
72273             inverse;
72274     
72275         store.add(reader.read(associationData).records);
72276     
72277         //now that we've added the related records to the hasMany association, set the inverse belongsTo
72278         //association on each of them if it exists
72279         inverse = this.associatedModel.prototype.associations.findBy(function(assoc){
72280             return assoc.type === 'belongsTo' && assoc.associatedName === record.$className;
72281         });
72282     
72283         //if the inverse association was found, set it now on each record we've just created
72284         if (inverse) {
72285             store.data.each(function(associatedRecord){
72286                 associatedRecord[inverse.instanceName] = record;
72287             });
72288         }
72289     }
72290 });
72291 /**
72292  * @class Ext.data.JsonP
72293  * @singleton
72294  * This class is used to create JSONP requests. JSONP is a mechanism that allows for making
72295  * requests for data cross domain. More information is available here:
72296  * http://en.wikipedia.org/wiki/JSONP
72297  */
72298 Ext.define('Ext.data.JsonP', {
72299     
72300     /* Begin Definitions */
72301     
72302     singleton: true,
72303     
72304     statics: {
72305         requestCount: 0,
72306         requests: {}
72307     },
72308     
72309     /* End Definitions */
72310     
72311     /**
72312      * @property timeout
72313      * @type Number
72314      * A default timeout for any JsonP requests. If the request has not completed in this time the
72315      * failure callback will be fired. The timeout is in ms. Defaults to <tt>30000</tt>.
72316      */
72317     timeout: 30000,
72318     
72319     /**
72320      * @property disableCaching
72321      * @type Boolean
72322      * True to add a unique cache-buster param to requests. Defaults to <tt>true</tt>.
72323      */
72324     disableCaching: true,
72325    
72326     /**
72327      * @property disableCachingParam 
72328      * @type String
72329      * Change the parameter which is sent went disabling caching through a cache buster. Defaults to <tt>'_dc'</tt>.
72330      */
72331     disableCachingParam: '_dc',
72332    
72333     /**
72334      * @property callbackKey
72335      * @type String
72336      * Specifies the GET parameter that will be sent to the server containing the function name to be executed when
72337      * the request completes. Defaults to <tt>callback</tt>. Thus, a common request will be in the form of
72338      * url?callback=Ext.data.JsonP.callback1
72339      */
72340     callbackKey: 'callback',
72341    
72342     /**
72343      * Makes a JSONP request.
72344      * @param {Object} options An object which may contain the following properties. Note that options will
72345      * take priority over any defaults that are specified in the class.
72346      * <ul>
72347      * <li><b>url</b> : String <div class="sub-desc">The URL to request.</div></li>
72348      * <li><b>params</b> : Object (Optional)<div class="sub-desc">An object containing a series of
72349      * key value pairs that will be sent along with the request.</div></li>
72350      * <li><b>timeout</b> : Number (Optional) <div class="sub-desc">See {@link #timeout}</div></li>
72351      * <li><b>callbackKey</b> : String (Optional) <div class="sub-desc">See {@link #callbackKey}</div></li>
72352      * <li><b>disableCaching</b> : Boolean (Optional) <div class="sub-desc">See {@link #disableCaching}</div></li>
72353      * <li><b>disableCachingParam</b> : String (Optional) <div class="sub-desc">See {@link #disableCachingParam}</div></li>
72354      * <li><b>success</b> : Function (Optional) <div class="sub-desc">A function to execute if the request succeeds.</div></li>
72355      * <li><b>failure</b> : Function (Optional) <div class="sub-desc">A function to execute if the request fails.</div></li>
72356      * <li><b>callback</b> : Function (Optional) <div class="sub-desc">A function to execute when the request 
72357      * completes, whether it is a success or failure.</div></li>
72358      * <li><b>scope</b> : Object (Optional)<div class="sub-desc">The scope in
72359      * which to execute the callbacks: The "this" object for the callback function. Defaults to the browser window.</div></li>
72360      * </ul>
72361      * @return {Object} request An object containing the request details.
72362      */
72363     request: function(options){
72364         options = Ext.apply({}, options);
72365        
72366         if (!options.url) {
72367             Ext.Error.raise('A url must be specified for a JSONP request.');
72368         }
72369         
72370         var me = this, 
72371             disableCaching = Ext.isDefined(options.disableCaching) ? options.disableCaching : me.disableCaching, 
72372             cacheParam = options.disableCachingParam || me.disableCachingParam, 
72373             id = ++me.statics().requestCount, 
72374             callbackName = 'callback' + id, 
72375             callbackKey = options.callbackKey || me.callbackKey, 
72376             timeout = Ext.isDefined(options.timeout) ? options.timeout : me.timeout, 
72377             params = Ext.apply({}, options.params), 
72378             url = options.url,
72379             request, 
72380             script;
72381             
72382         params[callbackKey] = 'Ext.data.JsonP.' + callbackName;
72383         if (disableCaching) {
72384             params[cacheParam] = new Date().getTime();
72385         }
72386         
72387         script = me.createScript(url, params);
72388         
72389         me.statics().requests[id] = request = {
72390             url: url,
72391             params: params,
72392             script: script,
72393             id: id,
72394             scope: options.scope,
72395             success: options.success,
72396             failure: options.failure,
72397             callback: options.callback,
72398             callbackName: callbackName
72399         };
72400         
72401         if (timeout > 0) {
72402             request.timeout = setTimeout(Ext.bind(me.handleTimeout, me, [request]), timeout);
72403         }
72404         
72405         me.setupErrorHandling(request);
72406         me[callbackName] = Ext.bind(me.handleResponse, me, [request], true);
72407         Ext.getHead().appendChild(script);
72408         return request;
72409     },
72410     
72411     /**
72412      * Abort a request. If the request parameter is not specified all open requests will
72413      * be aborted.
72414      * @param {Object/String} request (Optional) The request to abort
72415      */
72416     abort: function(request){
72417         var requests = this.statics().requests,
72418             key;
72419             
72420         if (request) {
72421             if (!request.id) {
72422                 request = requests[request];
72423             }
72424             this.abort(request);
72425         } else {
72426             for (key in requests) {
72427                 if (requests.hasOwnProperty(key)) {
72428                     this.abort(requests[key]);
72429                 }
72430             }
72431         }
72432     },
72433     
72434     /**
72435      * Sets up error handling for the script
72436      * @private
72437      * @param {Object} request The request
72438      */
72439     setupErrorHandling: function(request){
72440         request.script.onerror = Ext.bind(this.handleError, this, [request]);
72441     },
72442     
72443     /**
72444      * Handles any aborts when loading the script
72445      * @private
72446      * @param {Object} request The request
72447      */
72448     handleAbort: function(request){
72449         request.errorType = 'abort';
72450         this.handleResponse(null, request);
72451     },
72452     
72453     /**
72454      * Handles any script errors when loading the script
72455      * @private
72456      * @param {Object} request The request
72457      */
72458     handleError: function(request){
72459         request.errorType = 'error';
72460         this.handleResponse(null, request);
72461     },
72462  
72463     /**
72464      * Cleans up anu script handling errors
72465      * @private
72466      * @param {Object} request The request
72467      */
72468     cleanupErrorHandling: function(request){
72469         request.script.onerror = null;
72470     },
72471  
72472     /**
72473      * Handle any script timeouts
72474      * @private
72475      * @param {Object} request The request
72476      */
72477     handleTimeout: function(request){
72478         request.errorType = 'timeout';
72479         this.handleResponse(null, request);
72480     },
72481  
72482     /**
72483      * Handle a successful response
72484      * @private
72485      * @param {Object} result The result from the request
72486      * @param {Object} request The request
72487      */
72488     handleResponse: function(result, request){
72489  
72490         var success = true;
72491  
72492         if (request.timeout) {
72493             clearTimeout(request.timeout);
72494         }
72495         delete this[request.callbackName];
72496         delete this.statics()[request.id];
72497         this.cleanupErrorHandling(request);
72498         Ext.fly(request.script).remove();
72499  
72500         if (request.errorType) {
72501             success = false;
72502             Ext.callback(request.failure, request.scope, [request.errorType]);
72503         } else {
72504             Ext.callback(request.success, request.scope, [result]);
72505         }
72506         Ext.callback(request.callback, request.scope, [success, result, request.errorType]);
72507     },
72508     
72509     /**
72510      * Create the script tag
72511      * @private
72512      * @param {String} url The url of the request
72513      * @param {Object} params Any extra params to be sent
72514      */
72515     createScript: function(url, params) {
72516         var script = document.createElement('script');
72517         script.setAttribute("src", Ext.urlAppend(url, Ext.Object.toQueryString(params)));
72518         script.setAttribute("async", true);
72519         script.setAttribute("type", "text/javascript");
72520         return script;
72521     }
72522 });
72523
72524 /**
72525  * @class Ext.data.JsonPStore
72526  * @extends Ext.data.Store
72527  * @ignore
72528  * @private
72529  * <p><b>NOTE:</b> This class is in need of migration to the new API.</p>
72530  * <p>Small helper class to make creating {@link Ext.data.Store}s from different domain JSON data easier.
72531  * A JsonPStore will be automatically configured with a {@link Ext.data.reader.Json} and a {@link Ext.data.proxy.JsonP JsonPProxy}.</p>
72532  * <p>A store configuration would be something like:<pre><code>
72533 var store = new Ext.data.JsonPStore({
72534     // store configs
72535     autoDestroy: true,
72536     storeId: 'myStore',
72537
72538     // proxy configs
72539     url: 'get-images.php',
72540
72541     // reader configs
72542     root: 'images',
72543     idProperty: 'name',
72544     fields: ['name', 'url', {name:'size', type: 'float'}, {name:'lastmod', type:'date'}]
72545 });
72546  * </code></pre></p>
72547  * <p>This store is configured to consume a returned object of the form:<pre><code>
72548 stcCallback({
72549     images: [
72550         {name: 'Image one', url:'/GetImage.php?id=1', size:46.5, lastmod: new Date(2007, 10, 29)},
72551         {name: 'Image Two', url:'/GetImage.php?id=2', size:43.2, lastmod: new Date(2007, 10, 30)}
72552     ]
72553 })
72554  * </code></pre>
72555  * <p>Where stcCallback is the callback name passed in the request to the remote domain. See {@link Ext.data.proxy.JsonP JsonPProxy}
72556  * for details of how this works.</p>
72557  * An object literal of this form could also be used as the {@link #data} config option.</p>
72558  * <p><b>*Note:</b> Although not listed here, this class accepts all of the configuration options of
72559  * <b>{@link Ext.data.reader.Json JsonReader}</b> and <b>{@link Ext.data.proxy.JsonP JsonPProxy}</b>.</p>
72560  * @constructor
72561  * @param {Object} config
72562  * @xtype jsonpstore
72563  */
72564 Ext.define('Ext.data.JsonPStore', {
72565     extend: 'Ext.data.Store',
72566     alias : 'store.jsonp',
72567
72568     /**
72569      * @cfg {Ext.data.DataReader} reader @hide
72570      */
72571     constructor: function(config) {
72572         this.callParent(Ext.apply(config, {
72573             reader: Ext.create('Ext.data.reader.Json', config),
72574             proxy : Ext.create('Ext.data.proxy.JsonP', config)
72575         }));
72576     }
72577 });
72578
72579 /**
72580  * @class Ext.data.NodeInterface
72581  * This class is meant to be used as a set of methods that are applied to the prototype of a
72582  * Record to decorate it with a Node API. This means that models used in conjunction with a tree
72583  * will have all of the tree related methods available on the model. In general this class will
72584  * not be used directly by the developer.
72585  */
72586 Ext.define('Ext.data.NodeInterface', {
72587     requires: ['Ext.data.Field'],
72588     
72589     statics: {
72590         /**
72591          * This method allows you to decorate a Record's prototype to implement the NodeInterface.
72592          * This adds a set of methods, new events, new properties and new fields on every Record
72593          * with the same Model as the passed Record.
72594          * @param {Ext.data.Record} record The Record you want to decorate the prototype of.
72595          * @static
72596          */
72597         decorate: function(record) {
72598             if (!record.isNode) {
72599                 // Apply the methods and fields to the prototype
72600                 // @TODO: clean this up to use proper class system stuff
72601                 var mgr = Ext.ModelManager,
72602                     modelName = record.modelName,
72603                     modelClass = mgr.getModel(modelName),
72604                     idName = modelClass.prototype.idProperty,
72605                     instances = Ext.Array.filter(mgr.all.getArray(), function(item) {
72606                         return item.modelName == modelName;
72607                     }),
72608                     iln = instances.length,
72609                     newFields = [],
72610                     i, instance, jln, j, newField;
72611
72612                 // Start by adding the NodeInterface methods to the Model's prototype
72613                 modelClass.override(this.getPrototypeBody());
72614                 newFields = this.applyFields(modelClass, [
72615                     {name: idName,      type: 'string',  defaultValue: null},
72616                     {name: 'parentId',  type: 'string',  defaultValue: null},
72617                     {name: 'index',     type: 'int',     defaultValue: null},
72618                     {name: 'depth',     type: 'int',     defaultValue: 0}, 
72619                     {name: 'expanded',  type: 'bool',    defaultValue: false, persist: false},
72620                     {name: 'checked',   type: 'auto',    defaultValue: null},
72621                     {name: 'leaf',      type: 'bool',    defaultValue: false, persist: false},
72622                     {name: 'cls',       type: 'string',  defaultValue: null, persist: false},
72623                     {name: 'iconCls',   type: 'string',  defaultValue: null, persist: false},
72624                     {name: 'root',      type: 'boolean', defaultValue: false, persist: false},
72625                     {name: 'isLast',    type: 'boolean', defaultValue: false, persist: false},
72626                     {name: 'isFirst',   type: 'boolean', defaultValue: false, persist: false},
72627                     {name: 'allowDrop', type: 'boolean', defaultValue: true, persist: false},
72628                     {name: 'allowDrag', type: 'boolean', defaultValue: true, persist: false},
72629                     {name: 'loaded',    type: 'boolean', defaultValue: false, persist: false},
72630                     {name: 'loading',   type: 'boolean', defaultValue: false, persist: false},
72631                     {name: 'href',      type: 'string',  defaultValue: null, persist: false},
72632                     {name: 'hrefTarget',type: 'string',  defaultValue: null, persist: false},
72633                     {name: 'qtip',      type: 'string',  defaultValue: null, persist: false},
72634                     {name: 'qtitle',    type: 'string',  defaultValue: null, persist: false}
72635                 ]);
72636
72637                 jln = newFields.length;
72638                 // Set default values to all instances already out there
72639                 for (i = 0; i < iln; i++) {
72640                     instance = instances[i];
72641                     for (j = 0; j < jln; j++) {
72642                         newField = newFields[j];
72643                         if (instance.get(newField.name) === undefined) {
72644                             instance.data[newField.name] = newField.defaultValue;
72645                         }
72646                     }
72647                 }
72648             }
72649             
72650             Ext.applyIf(record, {
72651                 firstChild: null,
72652                 lastChild: null,
72653                 parentNode: null,
72654                 previousSibling: null,
72655                 nextSibling: null,
72656                 childNodes: []
72657             });
72658             // Commit any fields so the record doesn't show as dirty initially
72659             record.commit(true);
72660             
72661             record.enableBubble([
72662                 /**
72663                  * @event append
72664                  * Fires when a new child node is appended
72665                  * @param {Node} this This node
72666                  * @param {Node} node The newly appended node
72667                  * @param {Number} index The index of the newly appended node
72668                  */
72669                 "append",
72670
72671                 /**
72672                  * @event remove
72673                  * Fires when a child node is removed
72674                  * @param {Node} this This node
72675                  * @param {Node} node The removed node
72676                  */
72677                 "remove",
72678
72679                 /**
72680                  * @event move
72681                  * Fires when this node is moved to a new location in the tree
72682                  * @param {Node} this This node
72683                  * @param {Node} oldParent The old parent of this node
72684                  * @param {Node} newParent The new parent of this node
72685                  * @param {Number} index The index it was moved to
72686                  */
72687                 "move",
72688
72689                 /**
72690                  * @event insert
72691                  * Fires when a new child node is inserted.
72692                  * @param {Node} this This node
72693                  * @param {Node} node The child node inserted
72694                  * @param {Node} refNode The child node the node was inserted before
72695                  */
72696                 "insert",
72697
72698                 /**
72699                  * @event beforeappend
72700                  * Fires before a new child is appended, return false to cancel the append.
72701                  * @param {Node} this This node
72702                  * @param {Node} node The child node to be appended
72703                  */
72704                 "beforeappend",
72705
72706                 /**
72707                  * @event beforeremove
72708                  * Fires before a child is removed, return false to cancel the remove.
72709                  * @param {Node} this This node
72710                  * @param {Node} node The child node to be removed
72711                  */
72712                 "beforeremove",
72713
72714                 /**
72715                  * @event beforemove
72716                  * Fires before this node is moved to a new location in the tree. Return false to cancel the move.
72717                  * @param {Node} this This node
72718                  * @param {Node} oldParent The parent of this node
72719                  * @param {Node} newParent The new parent this node is moving to
72720                  * @param {Number} index The index it is being moved to
72721                  */
72722                 "beforemove",
72723
72724                  /**
72725                   * @event beforeinsert
72726                   * Fires before a new child is inserted, return false to cancel the insert.
72727                   * @param {Node} this This node
72728                   * @param {Node} node The child node to be inserted
72729                   * @param {Node} refNode The child node the node is being inserted before
72730                   */
72731                 "beforeinsert",
72732                 
72733                 /**
72734                  * @event expand
72735                  * Fires when this node is expanded.
72736                  * @param {Node} this The expanding node
72737                  */
72738                 "expand",
72739                 
72740                 /**
72741                  * @event collapse
72742                  * Fires when this node is collapsed.
72743                  * @param {Node} this The collapsing node
72744                  */
72745                 "collapse",
72746                 
72747                 /**
72748                  * @event beforeexpand
72749                  * Fires before this node is expanded.
72750                  * @param {Node} this The expanding node
72751                  */
72752                 "beforeexpand",
72753                 
72754                 /**
72755                  * @event beforecollapse
72756                  * Fires before this node is collapsed.
72757                  * @param {Node} this The collapsing node
72758                  */
72759                 "beforecollapse",
72760                 
72761                 /**
72762                  * @event beforecollapse
72763                  * Fires before this node is collapsed.
72764                  * @param {Node} this The collapsing node
72765                  */
72766                 "sort"
72767             ]);
72768             
72769             return record;
72770         },
72771         
72772         applyFields: function(modelClass, addFields) {
72773             var modelPrototype = modelClass.prototype,
72774                 fields = modelPrototype.fields,
72775                 keys = fields.keys,
72776                 ln = addFields.length,
72777                 addField, i, name,
72778                 newFields = [];
72779                 
72780             for (i = 0; i < ln; i++) {
72781                 addField = addFields[i];
72782                 if (!Ext.Array.contains(keys, addField.name)) {
72783                     addField = Ext.create('data.field', addField);
72784                     
72785                     newFields.push(addField);
72786                     fields.add(addField);
72787                 }
72788             }
72789             
72790             return newFields;
72791         },
72792         
72793         getPrototypeBody: function() {
72794             return {
72795                 isNode: true,
72796
72797                 /**
72798                  * Ensures that the passed object is an instance of a Record with the NodeInterface applied
72799                  * @return {Boolean}
72800                  */
72801                 createNode: function(node) {
72802                     if (Ext.isObject(node) && !node.isModel) {
72803                         node = Ext.ModelManager.create(node, this.modelName);
72804                     }
72805                     // Make sure the node implements the node interface
72806                     return Ext.data.NodeInterface.decorate(node);
72807                 },
72808                 
72809                 /**
72810                  * Returns true if this node is a leaf
72811                  * @return {Boolean}
72812                  */
72813                 isLeaf : function() {
72814                     return this.get('leaf') === true;
72815                 },
72816
72817                 /**
72818                  * Sets the first child of this node
72819                  * @private
72820                  * @param {Ext.data.NodeInterface} node
72821                  */
72822                 setFirstChild : function(node) {
72823                     this.firstChild = node;
72824                 },
72825
72826                 /**
72827                  * Sets the last child of this node
72828                  * @private
72829                  * @param {Ext.data.NodeInterface} node
72830                  */
72831                 setLastChild : function(node) {
72832                     this.lastChild = node;
72833                 },
72834
72835                 /**
72836                  * Updates general data of this node like isFirst, isLast, depth. This
72837                  * method is internally called after a node is moved. This shouldn't
72838                  * have to be called by the developer unless they are creating custom
72839                  * Tree plugins.
72840                  * @return {Boolean}
72841                  */
72842                 updateInfo: function(silent) {
72843                     var me = this,
72844                         isRoot = me.isRoot(),
72845                         parentNode = me.parentNode,
72846                         isFirst = (!parentNode ? true : parentNode.firstChild == me),
72847                         isLast = (!parentNode ? true : parentNode.lastChild == me),
72848                         depth = 0,
72849                         parent = me,
72850                         children = me.childNodes,
72851                         len = children.length,
72852                         i = 0;
72853
72854                     while (parent.parentNode) {
72855                         ++depth;
72856                         parent = parent.parentNode;
72857                     }                                            
72858                     
72859                     me.beginEdit();
72860                     me.set({
72861                         isFirst: isFirst,
72862                         isLast: isLast,
72863                         depth: depth,
72864                         index: parentNode ? parentNode.indexOf(me) : 0,
72865                         parentId: parentNode ? parentNode.getId() : null
72866                     });
72867                     me.endEdit(silent);
72868                     if (silent) {
72869                         me.commit();
72870                     }
72871                     
72872                     for (i = 0; i < len; i++) {
72873                         children[i].updateInfo(silent);
72874                     }
72875                 },
72876
72877                 /**
72878                  * Returns true if this node is the last child of its parent
72879                  * @return {Boolean}
72880                  */
72881                 isLast : function() {
72882                    return this.get('isLast');
72883                 },
72884
72885                 /**
72886                  * Returns true if this node is the first child of its parent
72887                  * @return {Boolean}
72888                  */
72889                 isFirst : function() {
72890                    return this.get('isFirst');
72891                 },
72892
72893                 /**
72894                  * Returns true if this node has one or more child nodes, else false.
72895                  * @return {Boolean}
72896                  */
72897                 hasChildNodes : function() {
72898                     return !this.isLeaf() && this.childNodes.length > 0;
72899                 },
72900
72901                 /**
72902                  * Returns true if this node has one or more child nodes, or if the <tt>expandable</tt>
72903                  * node attribute is explicitly specified as true (see {@link #attributes}), otherwise returns false.
72904                  * @return {Boolean}
72905                  */
72906                 isExpandable : function() {
72907                     return this.get('expandable') || this.hasChildNodes();
72908                 },
72909
72910                 /**
72911                  * <p>Insert node(s) as the last child node of this node.</p>
72912                  * <p>If the node was previously a child node of another parent node, it will be removed from that node first.</p>
72913                  * @param {Node/Array} node The node or Array of nodes to append
72914                  * @return {Node} The appended node if single append, or null if an array was passed
72915                  */
72916                 appendChild : function(node, suppressEvents, suppressNodeUpdate) {
72917                     var me = this,
72918                         i, ln,
72919                         index,
72920                         oldParent,
72921                         ps;
72922
72923                     // if passed an array or multiple args do them one by one
72924                     if (Ext.isArray(node)) {
72925                         for (i = 0, ln = node.length; i < ln; i++) {
72926                             me.appendChild(node[i]);
72927                         }
72928                     } else {
72929                         // Make sure it is a record
72930                         node = me.createNode(node);
72931                         
72932                         if (suppressEvents !== true && me.fireEvent("beforeappend", me, node) === false) {
72933                             return false;                         
72934                         }
72935
72936                         index = me.childNodes.length;
72937                         oldParent = node.parentNode;
72938
72939                         // it's a move, make sure we move it cleanly
72940                         if (oldParent) {
72941                             if (suppressEvents !== true && node.fireEvent("beforemove", node, oldParent, me, index) === false) {
72942                                 return false;
72943                             }
72944                             oldParent.removeChild(node, null, false, true);
72945                         }
72946
72947                         index = me.childNodes.length;
72948                         if (index === 0) {
72949                             me.setFirstChild(node);
72950                         }
72951
72952                         me.childNodes.push(node);
72953                         node.parentNode = me;
72954                         node.nextSibling = null;
72955
72956                         me.setLastChild(node);
72957                                                 
72958                         ps = me.childNodes[index - 1];
72959                         if (ps) {
72960                             node.previousSibling = ps;
72961                             ps.nextSibling = node;
72962                             ps.updateInfo(suppressNodeUpdate);
72963                         } else {
72964                             node.previousSibling = null;
72965                         }
72966
72967                         node.updateInfo(suppressNodeUpdate);
72968                         
72969                         // As soon as we append a child to this node, we are loaded
72970                         if (!me.isLoaded()) {
72971                             me.set('loaded', true);                            
72972                         }
72973                         // If this node didnt have any childnodes before, update myself
72974                         else if (me.childNodes.length === 1) {
72975                             me.set('loaded', me.isLoaded());
72976                         }
72977                         
72978                         if (suppressEvents !== true) {
72979                             me.fireEvent("append", me, node, index);
72980
72981                             if (oldParent) {
72982                                 node.fireEvent("move", node, oldParent, me, index);
72983                             }                            
72984                         }
72985
72986                         return node;
72987                     }
72988                 },
72989                 
72990                 /**
72991                  * Returns the bubble target for this node
72992                  * @private
72993                  * @return {Object} The bubble target
72994                  */
72995                 getBubbleTarget: function() {
72996                     return this.parentNode;
72997                 },
72998
72999                 /**
73000                  * Removes a child node from this node.
73001                  * @param {Node} node The node to remove
73002                  * @param {Boolean} destroy <tt>true</tt> to destroy the node upon removal. Defaults to <tt>false</tt>.
73003                  * @return {Node} The removed node
73004                  */
73005                 removeChild : function(node, destroy, suppressEvents, suppressNodeUpdate) {
73006                     var me = this,
73007                         index = me.indexOf(node);
73008                     
73009                     if (index == -1 || (suppressEvents !== true && me.fireEvent("beforeremove", me, node) === false)) {
73010                         return false;
73011                     }
73012
73013                     // remove it from childNodes collection
73014                     me.childNodes.splice(index, 1);
73015
73016                     // update child refs
73017                     if (me.firstChild == node) {
73018                         me.setFirstChild(node.nextSibling);
73019                     }
73020                     if (me.lastChild == node) {
73021                         me.setLastChild(node.previousSibling);
73022                     }
73023                     
73024                     // update siblings
73025                     if (node.previousSibling) {
73026                         node.previousSibling.nextSibling = node.nextSibling;
73027                         node.previousSibling.updateInfo(suppressNodeUpdate);
73028                     }
73029                     if (node.nextSibling) {
73030                         node.nextSibling.previousSibling = node.previousSibling;
73031                         node.nextSibling.updateInfo(suppressNodeUpdate);
73032                     }
73033
73034                     if (suppressEvents !== true) {
73035                         me.fireEvent("remove", me, node);
73036                     }
73037                     
73038                     
73039                     // If this node suddenly doesnt have childnodes anymore, update myself
73040                     if (!me.childNodes.length) {
73041                         me.set('loaded', me.isLoaded());
73042                     }
73043                     
73044                     if (destroy) {
73045                         node.destroy(true);
73046                     } else {
73047                         node.clear();
73048                     }
73049
73050                     return node;
73051                 },
73052
73053                 /**
73054                  * Creates a copy (clone) of this Node.
73055                  * @param {String} id (optional) A new id, defaults to this Node's id. See <code>{@link #id}</code>.
73056                  * @param {Boolean} deep (optional) <p>If passed as <code>true</code>, all child Nodes are recursively copied into the new Node.</p>
73057                  * <p>If omitted or false, the copy will have no child Nodes.</p>
73058                  * @return {Node} A copy of this Node.
73059                  */
73060                 copy: function(newId, deep) {
73061                     var me = this,
73062                         result = me.callOverridden(arguments),
73063                         len = me.childNodes ? me.childNodes.length : 0,
73064                         i;
73065
73066                     // Move child nodes across to the copy if required
73067                     if (deep) {
73068                         for (i = 0; i < len; i++) {
73069                             result.appendChild(me.childNodes[i].copy(true));
73070                         }
73071                     }
73072                     return result;
73073                 },
73074
73075                 /**
73076                  * Clear the node.
73077                  * @private
73078                  * @param {Boolean} destroy True to destroy the node.
73079                  */
73080                 clear : function(destroy) {
73081                     var me = this;
73082                     
73083                     // clear any references from the node
73084                     me.parentNode = me.previousSibling = me.nextSibling = null;
73085                     if (destroy) {
73086                         me.firstChild = me.lastChild = null;
73087                     }
73088                 },
73089
73090                 /**
73091                  * Destroys the node.
73092                  */
73093                 destroy : function(silent) {
73094                     /*
73095                      * Silent is to be used in a number of cases
73096                      * 1) When setRoot is called.
73097                      * 2) When destroy on the tree is called
73098                      * 3) For destroying child nodes on a node
73099                      */
73100                     var me = this;
73101                     
73102                     if (silent === true) {
73103                         me.clear(true);
73104                         Ext.each(me.childNodes, function(n) {
73105                             n.destroy(true);
73106                         });
73107                         me.childNodes = null;
73108                     } else {
73109                         me.remove(true);
73110                     }
73111
73112                     me.callOverridden();
73113                 },
73114
73115                 /**
73116                  * Inserts the first node before the second node in this nodes childNodes collection.
73117                  * @param {Node} node The node to insert
73118                  * @param {Node} refNode The node to insert before (if null the node is appended)
73119                  * @return {Node} The inserted node
73120                  */
73121                 insertBefore : function(node, refNode, suppressEvents) {
73122                     var me = this,
73123                         index     = me.indexOf(refNode),
73124                         oldParent = node.parentNode,
73125                         refIndex  = index,
73126                         ps;
73127                     
73128                     if (!refNode) { // like standard Dom, refNode can be null for append
73129                         return me.appendChild(node);
73130                     }
73131                     
73132                     // nothing to do
73133                     if (node == refNode) {
73134                         return false;
73135                     }
73136
73137                     // Make sure it is a record with the NodeInterface
73138                     node = me.createNode(node);
73139                     
73140                     if (suppressEvents !== true && me.fireEvent("beforeinsert", me, node, refNode) === false) {
73141                         return false;
73142                     }
73143                     
73144                     // when moving internally, indexes will change after remove
73145                     if (oldParent == me && me.indexOf(node) < index) {
73146                         refIndex--;
73147                     }
73148
73149                     // it's a move, make sure we move it cleanly
73150                     if (oldParent) {
73151                         if (suppressEvents !== true && node.fireEvent("beforemove", node, oldParent, me, index, refNode) === false) {
73152                             return false;
73153                         }
73154                         oldParent.removeChild(node);
73155                     }
73156
73157                     if (refIndex === 0) {
73158                         me.setFirstChild(node);
73159                     }
73160
73161                     me.childNodes.splice(refIndex, 0, node);
73162                     node.parentNode = me;
73163                     
73164                     node.nextSibling = refNode;
73165                     refNode.previousSibling = node;
73166                     
73167                     ps = me.childNodes[refIndex - 1];
73168                     if (ps) {
73169                         node.previousSibling = ps;
73170                         ps.nextSibling = node;
73171                         ps.updateInfo();
73172                     } else {
73173                         node.previousSibling = null;
73174                     }
73175                     
73176                     node.updateInfo();
73177                     
73178                     if (!me.isLoaded()) {
73179                         me.set('loaded', true);                            
73180                     }    
73181                     // If this node didnt have any childnodes before, update myself
73182                     else if (me.childNodes.length === 1) {
73183                         me.set('loaded', me.isLoaded());
73184                     }
73185
73186                     if (suppressEvents !== true) {
73187                         me.fireEvent("insert", me, node, refNode);
73188
73189                         if (oldParent) {
73190                             node.fireEvent("move", node, oldParent, me, refIndex, refNode);
73191                         }                        
73192                     }
73193
73194                     return node;
73195                 },
73196                 
73197                 /**
73198                  * Insert a node into this node
73199                  * @param {Number} index The zero-based index to insert the node at
73200                  * @param {Ext.data.Model} node The node to insert
73201                  * @return {Ext.data.Record} The record you just inserted
73202                  */    
73203                 insertChild: function(index, node) {
73204                     var sibling = this.childNodes[index];
73205                     if (sibling) {
73206                         return this.insertBefore(node, sibling);
73207                     }
73208                     else {
73209                         return this.appendChild(node);
73210                     }
73211                 },
73212
73213                 /**
73214                  * Removes this node from its parent
73215                  * @param {Boolean} destroy <tt>true</tt> to destroy the node upon removal. Defaults to <tt>false</tt>.
73216                  * @return {Node} this
73217                  */
73218                 remove : function(destroy, suppressEvents) {
73219                     var parentNode = this.parentNode;
73220
73221                     if (parentNode) {
73222                         parentNode.removeChild(this, destroy, suppressEvents, true);
73223                     }
73224                     return this;
73225                 },
73226
73227                 /**
73228                  * Removes all child nodes from this node.
73229                  * @param {Boolean} destroy <tt>true</tt> to destroy the node upon removal. Defaults to <tt>false</tt>.
73230                  * @return {Node} this
73231                  */
73232                 removeAll : function(destroy, suppressEvents) {
73233                     var cn = this.childNodes,
73234                         n;
73235
73236                     while ((n = cn[0])) {
73237                         this.removeChild(n, destroy, suppressEvents);
73238                     }
73239                     return this;
73240                 },
73241
73242                 /**
73243                  * Returns the child node at the specified index.
73244                  * @param {Number} index
73245                  * @return {Node}
73246                  */
73247                 getChildAt : function(index) {
73248                     return this.childNodes[index];
73249                 },
73250
73251                 /**
73252                  * Replaces one child node in this node with another.
73253                  * @param {Node} newChild The replacement node
73254                  * @param {Node} oldChild The node to replace
73255                  * @return {Node} The replaced node
73256                  */
73257                 replaceChild : function(newChild, oldChild, suppressEvents) {
73258                     var s = oldChild ? oldChild.nextSibling : null;
73259                     
73260                     this.removeChild(oldChild, suppressEvents);
73261                     this.insertBefore(newChild, s, suppressEvents);
73262                     return oldChild;
73263                 },
73264
73265                 /**
73266                  * Returns the index of a child node
73267                  * @param {Node} node
73268                  * @return {Number} The index of the node or -1 if it was not found
73269                  */
73270                 indexOf : function(child) {
73271                     return Ext.Array.indexOf(this.childNodes, child);
73272                 },
73273
73274                 /**
73275                  * Returns depth of this node (the root node has a depth of 0)
73276                  * @return {Number}
73277                  */
73278                 getDepth : function() {
73279                     return this.get('depth');
73280                 },
73281
73282                 /**
73283                  * Bubbles up the tree from this node, calling the specified function with each node. The arguments to the function
73284                  * will be the args provided or the current node. If the function returns false at any point,
73285                  * the bubble is stopped.
73286                  * @param {Function} fn The function to call
73287                  * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current Node.
73288                  * @param {Array} args (optional) The args to call the function with (default to passing the current Node)
73289                  */
73290                 bubble : function(fn, scope, args) {
73291                     var p = this;
73292                     while (p) {
73293                         if (fn.apply(scope || p, args || [p]) === false) {
73294                             break;
73295                         }
73296                         p = p.parentNode;
73297                     }
73298                 },
73299
73300                 cascade: function() {
73301                     if (Ext.isDefined(Ext.global.console)) {
73302                         Ext.global.console.warn('Ext.data.Node: cascade has been deprecated. Please use cascadeBy instead.');
73303                     }
73304                     return this.cascadeBy.apply(this, arguments);
73305                 },
73306
73307                 /**
73308                  * Cascades down the tree from this node, calling the specified function with each node. The arguments to the function
73309                  * will be the args provided or the current node. If the function returns false at any point,
73310                  * the cascade is stopped on that branch.
73311                  * @param {Function} fn The function to call
73312                  * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current Node.
73313                  * @param {Array} args (optional) The args to call the function with (default to passing the current Node)
73314                  */
73315                 cascadeBy : function(fn, scope, args) {
73316                     if (fn.apply(scope || this, args || [this]) !== false) {
73317                         var childNodes = this.childNodes,
73318                             length     = childNodes.length,
73319                             i;
73320
73321                         for (i = 0; i < length; i++) {
73322                             childNodes[i].cascadeBy(fn, scope, args);
73323                         }
73324                     }
73325                 },
73326
73327                 /**
73328                  * Interates the child nodes of this node, calling the specified function with each node. The arguments to the function
73329                  * will be the args provided or the current node. If the function returns false at any point,
73330                  * the iteration stops.
73331                  * @param {Function} fn The function to call
73332                  * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current Node in the iteration.
73333                  * @param {Array} args (optional) The args to call the function with (default to passing the current Node)
73334                  */
73335                 eachChild : function(fn, scope, args) {
73336                     var childNodes = this.childNodes,
73337                         length     = childNodes.length,
73338                         i;
73339
73340                     for (i = 0; i < length; i++) {
73341                         if (fn.apply(scope || this, args || [childNodes[i]]) === false) {
73342                             break;
73343                         }
73344                     }
73345                 },
73346
73347                 /**
73348                  * Finds the first child that has the attribute with the specified value.
73349                  * @param {String} attribute The attribute name
73350                  * @param {Mixed} value The value to search for
73351                  * @param {Boolean} deep (Optional) True to search through nodes deeper than the immediate children
73352                  * @return {Node} The found child or null if none was found
73353                  */
73354                 findChild : function(attribute, value, deep) {
73355                     return this.findChildBy(function() {
73356                         return this.get(attribute) == value;
73357                     }, null, deep);
73358                 },
73359
73360                 /**
73361                  * Finds the first child by a custom function. The child matches if the function passed returns <code>true</code>.
73362                  * @param {Function} fn A function which must return <code>true</code> if the passed Node is the required Node.
73363                  * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the Node being tested.
73364                  * @param {Boolean} deep (Optional) True to search through nodes deeper than the immediate children
73365                  * @return {Node} The found child or null if none was found
73366                  */
73367                 findChildBy : function(fn, scope, deep) {
73368                     var cs = this.childNodes,
73369                         len = cs.length,
73370                         i = 0, n, res;
73371
73372                     for (; i < len; i++) {
73373                         n = cs[i];
73374                         if (fn.call(scope || n, n) === true) {
73375                             return n;
73376                         }
73377                         else if (deep) {
73378                             res = n.findChildBy(fn, scope, deep);
73379                             if (res !== null) {
73380                                 return res;
73381                             }
73382                         }
73383                     }
73384
73385                     return null;
73386                 },
73387
73388                 /**
73389                  * Returns true if this node is an ancestor (at any point) of the passed node.
73390                  * @param {Node} node
73391                  * @return {Boolean}
73392                  */
73393                 contains : function(node) {
73394                     return node.isAncestor(this);
73395                 },
73396
73397                 /**
73398                  * Returns true if the passed node is an ancestor (at any point) of this node.
73399                  * @param {Node} node
73400                  * @return {Boolean}
73401                  */
73402                 isAncestor : function(node) {
73403                     var p = this.parentNode;
73404                     while (p) {
73405                         if (p == node) {
73406                             return true;
73407                         }
73408                         p = p.parentNode;
73409                     }
73410                     return false;
73411                 },
73412
73413                 /**
73414                  * Sorts this nodes children using the supplied sort function.
73415                  * @param {Function} fn A function which, when passed two Nodes, returns -1, 0 or 1 depending upon required sort order.
73416                  * @param {Boolean} recursive Whether or not to apply this sort recursively
73417                  * @param {Boolean} suppressEvent Set to true to not fire a sort event.
73418                  */
73419                 sort : function(sortFn, recursive, suppressEvent) {
73420                     var cs  = this.childNodes,
73421                         ln = cs.length,
73422                         i, n;
73423                     
73424                     if (ln > 0) {
73425                         Ext.Array.sort(cs, sortFn);
73426                         for (i = 0; i < ln; i++) {
73427                             n = cs[i];
73428                             n.previousSibling = cs[i-1];
73429                             n.nextSibling = cs[i+1];
73430                         
73431                             if (i === 0) {
73432                                 this.setFirstChild(n);
73433                                 n.updateInfo();
73434                             }
73435                             if (i == ln - 1) {
73436                                 this.setLastChild(n);
73437                                 n.updateInfo();
73438                             }
73439                             if (recursive && !n.isLeaf()) {
73440                                 n.sort(sortFn, true, true);
73441                             }
73442                         }
73443                         
73444                         if (suppressEvent !== true) {
73445                             this.fireEvent('sort', this, cs);
73446                         }
73447                     }
73448                 },
73449                         
73450                 /**
73451                  * Returns true if this node is expaned
73452                  * @return {Boolean}
73453                  */        
73454                 isExpanded: function() {
73455                     return this.get('expanded');
73456                 },
73457                 
73458                 /**
73459                  * Returns true if this node is loaded
73460                  * @return {Boolean}
73461                  */ 
73462                 isLoaded: function() {
73463                     return this.get('loaded');
73464                 },
73465
73466                 /**
73467                  * Returns true if this node is loading
73468                  * @return {Boolean}
73469                  */ 
73470                 isLoading: function() {
73471                     return this.get('loading');
73472                 },
73473                                 
73474                 /**
73475                  * Returns true if this node is the root node
73476                  * @return {Boolean}
73477                  */ 
73478                 isRoot: function() {
73479                     return !this.parentNode;
73480                 },
73481                 
73482                 /**
73483                  * Returns true if this node is visible
73484                  * @return {Boolean}
73485                  */ 
73486                 isVisible: function() {
73487                     var parent = this.parentNode;
73488                     while (parent) {
73489                         if (!parent.isExpanded()) {
73490                             return false;
73491                         }
73492                         parent = parent.parentNode;
73493                     }
73494                     return true;
73495                 },
73496                 
73497                 /**
73498                  * Expand this node.
73499                  * @param {Function} recursive (Optional) True to recursively expand all the children
73500                  * @param {Function} callback (Optional) The function to execute once the expand completes
73501                  * @param {Object} scope (Optional) The scope to run the callback in
73502                  */
73503                 expand: function(recursive, callback, scope) {
73504                     var me = this;
73505
73506                     // all paths must call the callback (eventually) or things like
73507                     // selectPath fail
73508
73509                     // First we start by checking if this node is a parent
73510                     if (!me.isLeaf()) {
73511                         // Now we check if this record is already expanding or expanded
73512                         if (!me.isLoading() && !me.isExpanded()) {
73513                             // The TreeStore actually listens for the beforeexpand method and checks
73514                             // whether we have to asynchronously load the children from the server
73515                             // first. Thats why we pass a callback function to the event that the
73516                             // store can call once it has loaded and parsed all the children.
73517                             me.fireEvent('beforeexpand', me, function(records) {
73518                                 me.set('expanded', true); 
73519                                 me.fireEvent('expand', me, me.childNodes, false);
73520                                 
73521                                 // Call the expandChildren method if recursive was set to true 
73522                                 if (recursive) {
73523                                     me.expandChildren(true, callback, scope);
73524                                 }
73525                                 else {
73526                                     Ext.callback(callback, scope || me, [me.childNodes]);                                
73527                                 }
73528                             }, me);                            
73529                         }
73530                         // If it is is already expanded but we want to recursively expand then call expandChildren
73531                         else if (recursive) {
73532                             me.expandChildren(true, callback, scope);
73533                         }
73534                         else {
73535                             Ext.callback(callback, scope || me, [me.childNodes]);
73536                         }
73537
73538                         // TODO - if the node isLoading, we probably need to defer the
73539                         // callback until it is loaded (e.g., selectPath would need us
73540                         // to not make the callback until the childNodes exist).
73541                     }
73542                     // If it's not then we fire the callback right away
73543                     else {
73544                         Ext.callback(callback, scope || me); // leaf = no childNodes
73545                     }
73546                 },
73547                 
73548                 /**
73549                  * Expand all the children of this node.
73550                  * @param {Function} recursive (Optional) True to recursively expand all the children
73551                  * @param {Function} callback (Optional) The function to execute once all the children are expanded
73552                  * @param {Object} scope (Optional) The scope to run the callback in
73553                  */
73554                 expandChildren: function(recursive, callback, scope) {
73555                     var me = this,
73556                         i = 0,
73557                         nodes = me.childNodes,
73558                         ln = nodes.length,
73559                         node,
73560                         expanding = 0;
73561
73562                     for (; i < ln; ++i) {
73563                         node = nodes[i];
73564                         if (!node.isLeaf() && !node.isExpanded()) {
73565                             expanding++;
73566                             nodes[i].expand(recursive, function () {
73567                                 expanding--;
73568                                 if (callback && !expanding) {
73569                                     Ext.callback(callback, scope || me, me.childNodes); 
73570                                 }
73571                             });                            
73572                         }
73573                     }
73574                     
73575                     if (!expanding && callback) {
73576                         Ext.callback(callback, scope || me, me.childNodes);
73577                     }
73578                 },
73579
73580                 /**
73581                  * Collapse this node.
73582                  * @param {Function} recursive (Optional) True to recursively collapse all the children
73583                  * @param {Function} callback (Optional) The function to execute once the collapse completes
73584                  * @param {Object} scope (Optional) The scope to run the callback in
73585                  */
73586                 collapse: function(recursive, callback, scope) {
73587                     var me = this;
73588
73589                     // First we start by checking if this node is a parent
73590                     if (!me.isLeaf()) {
73591                         // Now we check if this record is already collapsing or collapsed
73592                         if (!me.collapsing && me.isExpanded()) {
73593                             me.fireEvent('beforecollapse', me, function(records) {
73594                                 me.set('expanded', false); 
73595                                 me.fireEvent('collapse', me, me.childNodes, false);
73596                                 
73597                                 // Call the collapseChildren method if recursive was set to true 
73598                                 if (recursive) {
73599                                     me.collapseChildren(true, callback, scope);
73600                                 }
73601                                 else {
73602                                     Ext.callback(callback, scope || me, [me.childNodes]);                                
73603                                 }
73604                             }, me);                            
73605                         }
73606                         // If it is is already collapsed but we want to recursively collapse then call collapseChildren
73607                         else if (recursive) {
73608                             me.collapseChildren(true, callback, scope);
73609                         }
73610                     }
73611                     // If it's not then we fire the callback right away
73612                     else {
73613                         Ext.callback(callback, scope || me, me.childNodes); 
73614                     }
73615                 },
73616                 
73617                 /**
73618                  * Collapse all the children of this node.
73619                  * @param {Function} recursive (Optional) True to recursively collapse all the children
73620                  * @param {Function} callback (Optional) The function to execute once all the children are collapsed
73621                  * @param {Object} scope (Optional) The scope to run the callback in
73622                  */
73623                 collapseChildren: function(recursive, callback, scope) {
73624                     var me = this,
73625                         i = 0,
73626                         nodes = me.childNodes,
73627                         ln = nodes.length,
73628                         node,
73629                         collapsing = 0;
73630
73631                     for (; i < ln; ++i) {
73632                         node = nodes[i];
73633                         if (!node.isLeaf() && node.isExpanded()) {
73634                             collapsing++;
73635                             nodes[i].collapse(recursive, function () {
73636                                 collapsing--;
73637                                 if (callback && !collapsing) {
73638                                     Ext.callback(callback, scope || me, me.childNodes); 
73639                                 }
73640                             });                            
73641                         }
73642                     }
73643                     
73644                     if (!collapsing && callback) {
73645                         Ext.callback(callback, scope || me, me.childNodes);
73646                     }
73647                 }
73648             };
73649         }
73650     }
73651 });
73652 /**
73653  * @class Ext.data.NodeStore
73654  * @extends Ext.data.AbstractStore
73655  * Node Store
73656  * @ignore
73657  */
73658 Ext.define('Ext.data.NodeStore', {
73659     extend: 'Ext.data.Store',
73660     alias: 'store.node',
73661     requires: ['Ext.data.NodeInterface'],
73662     
73663     /**
73664      * @cfg {Ext.data.Record} node The Record you want to bind this Store to. Note that
73665      * this record will be decorated with the Ext.data.NodeInterface if this is not the
73666      * case yet.
73667      */
73668     node: null,
73669     
73670     /**
73671      * @cfg {Boolean} recursive Set this to true if you want this NodeStore to represent
73672      * all the descendents of the node in its flat data collection. This is useful for
73673      * rendering a tree structure to a DataView and is being used internally by
73674      * the TreeView. Any records that are moved, removed, inserted or appended to the
73675      * node at any depth below the node this store is bound to will be automatically
73676      * updated in this Store's internal flat data structure.
73677      */
73678     recursive: false,
73679     
73680     /** 
73681      * @cfg {Boolean} rootVisible <tt>false</tt> to not include the root node in this Stores collection (defaults to <tt>true</tt>)
73682      */    
73683     rootVisible: false,
73684     
73685     constructor: function(config) {
73686         var me = this,
73687             node;
73688             
73689         config = config || {};
73690         Ext.apply(me, config);
73691         
73692         if (Ext.isDefined(me.proxy)) {
73693             Ext.Error.raise("A NodeStore cannot be bound to a proxy. Instead bind it to a record " +
73694                             "decorated with the NodeInterface by setting the node config.");
73695         }
73696
73697         config.proxy = {type: 'proxy'};
73698         me.callParent([config]);
73699
73700         me.addEvents('expand', 'collapse', 'beforeexpand', 'beforecollapse');
73701         
73702         node = me.node;
73703         if (node) {
73704             me.node = null;
73705             me.setNode(node);
73706         }
73707     },
73708     
73709     setNode: function(node) {
73710         var me = this;
73711         
73712         if (me.node && me.node != node) {
73713             // We want to unbind our listeners on the old node
73714             me.mun(me.node, {
73715                 expand: me.onNodeExpand,
73716                 collapse: me.onNodeCollapse,
73717                 append: me.onNodeAppend,
73718                 insert: me.onNodeInsert,
73719                 remove: me.onNodeRemove,
73720                 sort: me.onNodeSort,
73721                 scope: me
73722             });
73723             me.node = null;
73724         }
73725         
73726         if (node) {
73727             Ext.data.NodeInterface.decorate(node);
73728             me.removeAll();
73729             if (me.rootVisible) {
73730                 me.add(node);
73731             }
73732             me.mon(node, {
73733                 expand: me.onNodeExpand,
73734                 collapse: me.onNodeCollapse,
73735                 append: me.onNodeAppend,
73736                 insert: me.onNodeInsert,
73737                 remove: me.onNodeRemove,
73738                 sort: me.onNodeSort,
73739                 scope: me
73740             });
73741             me.node = node;
73742             if (node.isExpanded() && node.isLoaded()) {
73743                 me.onNodeExpand(node, node.childNodes, true);
73744             }
73745         }
73746     },
73747     
73748     onNodeSort: function(node, childNodes) {
73749         var me = this;
73750         
73751         if ((me.indexOf(node) !== -1 || (node === me.node && !me.rootVisible) && node.isExpanded())) {
73752             me.onNodeCollapse(node, childNodes, true);
73753             me.onNodeExpand(node, childNodes, true);
73754         }
73755     },
73756     
73757     onNodeExpand: function(parent, records, suppressEvent) {
73758         var me = this,
73759             insertIndex = me.indexOf(parent) + 1,
73760             ln = records ? records.length : 0,
73761             i, record;
73762             
73763         if (!me.recursive && parent !== me.node) {
73764             return;
73765         }
73766         
73767         if (!me.isVisible(parent)) {
73768             return;
73769         }
73770
73771         if (!suppressEvent && me.fireEvent('beforeexpand', parent, records, insertIndex) === false) {
73772             return;
73773         }
73774         
73775         if (ln) {
73776             me.insert(insertIndex, records);
73777             for (i = 0; i < ln; i++) {
73778                 record = records[i];
73779                 if (record.isExpanded()) {
73780                     if (record.isLoaded()) {
73781                         // Take a shortcut                        
73782                         me.onNodeExpand(record, record.childNodes, true);
73783                     }
73784                     else {
73785                         record.set('expanded', false);
73786                         record.expand();
73787                     }
73788                 }
73789             }
73790         }
73791
73792         if (!suppressEvent) {
73793             me.fireEvent('expand', parent, records);
73794         }
73795     },
73796
73797     onNodeCollapse: function(parent, records, suppressEvent) {
73798         var me = this,
73799             ln = records.length,
73800             collapseIndex = me.indexOf(parent) + 1,
73801             i, record;
73802             
73803         if (!me.recursive && parent !== me.node) {
73804             return;
73805         }
73806         
73807         if (!suppressEvent && me.fireEvent('beforecollapse', parent, records, collapseIndex) === false) {
73808             return;
73809         }
73810
73811         for (i = 0; i < ln; i++) {
73812             record = records[i];
73813             me.remove(record);
73814             if (record.isExpanded()) {
73815                 me.onNodeCollapse(record, record.childNodes, true);
73816             }
73817         }
73818         
73819         if (!suppressEvent) {
73820             me.fireEvent('collapse', parent, records, collapseIndex);
73821         }
73822     },
73823     
73824     onNodeAppend: function(parent, node, index) {
73825         var me = this,
73826             refNode, sibling;
73827
73828         if (me.isVisible(node)) {
73829             if (index === 0) {
73830                 refNode = parent;
73831             } else {
73832                 sibling = node.previousSibling;
73833                 while (sibling.isExpanded() && sibling.lastChild) {
73834                     sibling = sibling.lastChild;
73835                 }
73836                 refNode = sibling;
73837             }
73838             me.insert(me.indexOf(refNode) + 1, node);
73839             if (!node.isLeaf() && node.isExpanded()) {
73840                 if (node.isLoaded()) {
73841                     // Take a shortcut                        
73842                     me.onNodeExpand(node, node.childNodes, true);
73843                 }
73844                 else {
73845                     node.set('expanded', false);
73846                     node.expand();
73847                 }
73848             }
73849         } 
73850     },
73851     
73852     onNodeInsert: function(parent, node, refNode) {
73853         var me = this,
73854             index = this.indexOf(refNode);
73855             
73856         if (index != -1 && me.isVisible(node)) {
73857             me.insert(index, node);
73858             if (!node.isLeaf() && node.isExpanded()) {
73859                 if (node.isLoaded()) {
73860                     // Take a shortcut                        
73861                     me.onNodeExpand(node, node.childNodes, true);
73862                 }
73863                 else {
73864                     node.set('expanded', false);
73865                     node.expand();
73866                 }
73867             }
73868         }
73869     },
73870     
73871     onNodeRemove: function(parent, node, index) {
73872         var me = this;
73873         if (me.indexOf(node) != -1) {
73874             if (!node.isLeaf() && node.isExpanded()) {
73875                 me.onNodeCollapse(node, node.childNodes, true);
73876             }            
73877             me.remove(node);
73878         }
73879     },
73880     
73881     isVisible: function(node) {
73882         var parent = node.parentNode;
73883         while (parent) {
73884             if (parent === this.node && !this.rootVisible && parent.isExpanded()) {
73885                 return true;
73886             }
73887             
73888             if (this.indexOf(parent) === -1 || !parent.isExpanded()) {
73889                 return false;
73890             }
73891             
73892             parent = parent.parentNode;
73893         }
73894         return true;
73895     }
73896 });
73897 /**
73898  * @author Ed Spencer
73899  * @class Ext.data.Request
73900  * @extends Object
73901  * 
73902  * <p>Simple class that represents a Request that will be made by any {@link Ext.data.proxy.Server} subclass.
73903  * All this class does is standardize the representation of a Request as used by any ServerProxy subclass,
73904  * it does not contain any actual logic or perform the request itself.</p>
73905  * 
73906  * @constructor
73907  * @param {Object} config Optional config object
73908  */
73909 Ext.define('Ext.data.Request', {
73910     /**
73911      * @cfg {String} action The name of the action this Request represents. Usually one of 'create', 'read', 'update' or 'destroy'
73912      */
73913     action: undefined,
73914     
73915     /**
73916      * @cfg {Object} params HTTP request params. The Proxy and its Writer have access to and can modify this object.
73917      */
73918     params: undefined,
73919     
73920     /**
73921      * @cfg {String} method The HTTP method to use on this Request (defaults to 'GET'). Should be one of 'GET', 'POST', 'PUT' or 'DELETE'
73922      */
73923     method: 'GET',
73924     
73925     /**
73926      * @cfg {String} url The url to access on this Request
73927      */
73928     url: undefined,
73929
73930     constructor: function(config) {
73931         Ext.apply(this, config);
73932     }
73933 });
73934 /**
73935  * @class Ext.data.Tree
73936  * 
73937  * This class is used as a container for a series of nodes. The nodes themselves maintain
73938  * the relationship between parent/child. The tree itself acts as a manager. It gives functionality
73939  * to retrieve a node by its identifier: {@link #getNodeById}. 
73940  *
73941  * The tree also relays events from any of it's child nodes, allowing them to be handled in a 
73942  * centralized fashion. In general this class is not used directly, rather used internally 
73943  * by other parts of the framework.
73944  *
73945  * @constructor
73946  * @param {Node} root (optional) The root node
73947  */
73948 Ext.define('Ext.data.Tree', {
73949     alias: 'data.tree',
73950     
73951     mixins: {
73952         observable: "Ext.util.Observable"
73953     },
73954
73955     /**
73956      * The root node for this tree
73957      * @type Node
73958      */
73959     root: null,
73960         
73961     constructor: function(root) {
73962         var me = this;
73963         
73964         me.nodeHash = {};
73965
73966         me.mixins.observable.constructor.call(me);
73967                         
73968         if (root) {
73969             me.setRootNode(root);
73970         }
73971     },
73972
73973     /**
73974      * Returns the root node for this tree.
73975      * @return {Ext.data.NodeInterface}
73976      */
73977     getRootNode : function() {
73978         return this.root;
73979     },
73980
73981     /**
73982      * Sets the root node for this tree.
73983      * @param {Ext.data.NodeInterface} node
73984      * @return {Ext.data.NodeInterface} The root node
73985      */
73986     setRootNode : function(node) {
73987         var me = this;
73988         
73989         me.root = node;
73990         Ext.data.NodeInterface.decorate(node);
73991         
73992         if (me.fireEvent('beforeappend', null, node) !== false) {
73993             node.set('root', true);
73994             node.updateInfo();
73995             
73996             me.relayEvents(node, [
73997                 /**
73998                  * @event append
73999                  * Fires when a new child node is appended to a node in this tree.
74000                  * @param {Tree} tree The owner tree
74001                  * @param {Node} parent The parent node
74002                  * @param {Node} node The newly appended node
74003                  * @param {Number} index The index of the newly appended node
74004                  */
74005                 "append",
74006
74007                 /**
74008                  * @event remove
74009                  * Fires when a child node is removed from a node in this tree.
74010                  * @param {Tree} tree The owner tree
74011                  * @param {Node} parent The parent node
74012                  * @param {Node} node The child node removed
74013                  */
74014                 "remove",
74015
74016                 /**
74017                  * @event move
74018                  * Fires when a node is moved to a new location in the tree
74019                  * @param {Tree} tree The owner tree
74020                  * @param {Node} node The node moved
74021                  * @param {Node} oldParent The old parent of this node
74022                  * @param {Node} newParent The new parent of this node
74023                  * @param {Number} index The index it was moved to
74024                  */
74025                 "move",
74026
74027                 /**
74028                  * @event insert
74029                  * Fires when a new child node is inserted in a node in this tree.
74030                  * @param {Tree} tree The owner tree
74031                  * @param {Node} parent The parent node
74032                  * @param {Node} node The child node inserted
74033                  * @param {Node} refNode The child node the node was inserted before
74034                  */
74035                 "insert",
74036
74037                 /**
74038                  * @event beforeappend
74039                  * Fires before a new child is appended to a node in this tree, return false to cancel the append.
74040                  * @param {Tree} tree The owner tree
74041                  * @param {Node} parent The parent node
74042                  * @param {Node} node The child node to be appended
74043                  */
74044                 "beforeappend",
74045
74046                 /**
74047                  * @event beforeremove
74048                  * Fires before a child is removed from a node in this tree, return false to cancel the remove.
74049                  * @param {Tree} tree The owner tree
74050                  * @param {Node} parent The parent node
74051                  * @param {Node} node The child node to be removed
74052                  */
74053                 "beforeremove",
74054
74055                 /**
74056                  * @event beforemove
74057                  * Fires before a node is moved to a new location in the tree. Return false to cancel the move.
74058                  * @param {Tree} tree The owner tree
74059                  * @param {Node} node The node being moved
74060                  * @param {Node} oldParent The parent of the node
74061                  * @param {Node} newParent The new parent the node is moving to
74062                  * @param {Number} index The index it is being moved to
74063                  */
74064                 "beforemove",
74065
74066                 /**
74067                  * @event beforeinsert
74068                  * Fires before a new child is inserted in a node in this tree, return false to cancel the insert.
74069                  * @param {Tree} tree The owner tree
74070                  * @param {Node} parent The parent node
74071                  * @param {Node} node The child node to be inserted
74072                  * @param {Node} refNode The child node the node is being inserted before
74073                  */
74074                 "beforeinsert",
74075
74076                  /**
74077                   * @event expand
74078                   * Fires when this node is expanded.
74079                   * @param {Node} this The expanding node
74080                   */
74081                  "expand",
74082
74083                  /**
74084                   * @event collapse
74085                   * Fires when this node is collapsed.
74086                   * @param {Node} this The collapsing node
74087                   */
74088                  "collapse",
74089
74090                  /**
74091                   * @event beforeexpand
74092                   * Fires before this node is expanded.
74093                   * @param {Node} this The expanding node
74094                   */
74095                  "beforeexpand",
74096
74097                  /**
74098                   * @event beforecollapse
74099                   * Fires before this node is collapsed.
74100                   * @param {Node} this The collapsing node
74101                   */
74102                  "beforecollapse" ,
74103
74104                  /**
74105                   * @event rootchange
74106                   * Fires whenever the root node is changed in the tree.
74107                   * @param {Ext.data.Model} root The new root
74108                   */
74109                  "rootchange"
74110             ]);
74111             
74112             node.on({
74113                 scope: me,
74114                 insert: me.onNodeInsert,
74115                 append: me.onNodeAppend,
74116                 remove: me.onNodeRemove
74117             });
74118
74119             me.registerNode(node);        
74120             me.fireEvent('append', null, node);
74121             me.fireEvent('rootchange', node);
74122         }
74123             
74124         return node;
74125     },
74126     
74127     /**
74128      * Flattens all the nodes in the tree into an array.
74129      * @private
74130      * @return {Array} The flattened nodes.
74131      */
74132     flatten: function(){
74133         var nodes = [],
74134             hash = this.nodeHash,
74135             key;
74136             
74137         for (key in hash) {
74138             if (hash.hasOwnProperty(key)) {
74139                 nodes.push(hash[key]);
74140             }
74141         }
74142         return nodes;
74143     },
74144     
74145     /**
74146      * Fired when a node is inserted into the root or one of it's children
74147      * @private
74148      * @param {Ext.data.NodeInterface} parent The parent node
74149      * @param {Ext.data.NodeInterface} node The inserted node
74150      */
74151     onNodeInsert: function(parent, node) {
74152         this.registerNode(node);
74153     },
74154     
74155     /**
74156      * Fired when a node is appended into the root or one of it's children
74157      * @private
74158      * @param {Ext.data.NodeInterface} parent The parent node
74159      * @param {Ext.data.NodeInterface} node The appended node
74160      */
74161     onNodeAppend: function(parent, node) {
74162         this.registerNode(node);
74163     },
74164     
74165     /**
74166      * Fired when a node is removed from the root or one of it's children
74167      * @private
74168      * @param {Ext.data.NodeInterface} parent The parent node
74169      * @param {Ext.data.NodeInterface} node The removed node
74170      */
74171     onNodeRemove: function(parent, node) {
74172         this.unregisterNode(node);
74173     },
74174
74175     /**
74176      * Gets a node in this tree by its id.
74177      * @param {String} id
74178      * @return {Ext.data.NodeInterface} The match node.
74179      */
74180     getNodeById : function(id) {
74181         return this.nodeHash[id];
74182     },
74183
74184     /**
74185      * Registers a node with the tree
74186      * @private
74187      * @param {Ext.data.NodeInterface} The node to register
74188      */
74189     registerNode : function(node) {
74190         this.nodeHash[node.getId() || node.internalId] = node;
74191     },
74192
74193     /**
74194      * Unregisters a node with the tree
74195      * @private
74196      * @param {Ext.data.NodeInterface} The node to unregister
74197      */
74198     unregisterNode : function(node) {
74199         delete this.nodeHash[node.getId() || node.internalId];
74200     },
74201     
74202     /**
74203      * Sorts this tree
74204      * @private
74205      * @param {Function} sorterFn The function to use for sorting
74206      * @param {Boolean} recursive True to perform recursive sorting
74207      */
74208     sort: function(sorterFn, recursive) {
74209         this.getRootNode().sort(sorterFn, recursive);
74210     },
74211     
74212      /**
74213      * Filters this tree
74214      * @private
74215      * @param {Function} sorterFn The function to use for filtering
74216      * @param {Boolean} recursive True to perform recursive filtering
74217      */
74218     filter: function(filters, recursive) {
74219         this.getRootNode().filter(filters, recursive);
74220     }
74221 });
74222 /**
74223  * @class Ext.data.TreeStore
74224  * @extends Ext.data.AbstractStore
74225  * 
74226  * The TreeStore is a store implementation that is backed by by an {@link Ext.data.Tree}.
74227  * It provides convenience methods for loading nodes, as well as the ability to use
74228  * the hierarchical tree structure combined with a store. This class is generally used
74229  * in conjunction with {@link Ext.tree.Panel}. This class also relays many events from
74230  * the Tree for convenience.
74231  * 
74232  * ## Using Models
74233  * If no Model is specified, an implicit model will be created that implements {@link Ext.data.NodeInterface}.
74234  * The standard Tree fields will also be copied onto the Model for maintaining their state.
74235  * 
74236  * ## Reading Nested Data
74237  * For the tree to read nested data, the {@link Ext.data.Reader} must be configured with a root property,
74238  * so the reader can find nested data for each node. If a root is not specified, it will default to
74239  * 'children'.
74240  */
74241 Ext.define('Ext.data.TreeStore', {
74242     extend: 'Ext.data.AbstractStore',
74243     alias: 'store.tree',
74244     requires: ['Ext.data.Tree', 'Ext.data.NodeInterface', 'Ext.data.NodeStore'],
74245
74246     /**
74247      * @cfg {Boolean} clearOnLoad (optional) Default to true. Remove previously existing
74248      * child nodes before loading.
74249      */
74250     clearOnLoad : true,
74251
74252     /**
74253      * @cfg {String} nodeParam The name of the parameter sent to the server which contains
74254      * the identifier of the node. Defaults to <tt>'node'</tt>.
74255      */
74256     nodeParam: 'node',
74257
74258     /**
74259      * @cfg {String} defaultRootId
74260      * The default root id. Defaults to 'root'
74261      */
74262     defaultRootId: 'root',
74263     
74264     /**
74265      * @cfg {String} defaultRootProperty
74266      * The root property to specify on the reader if one is not explicitly defined.
74267      */
74268     defaultRootProperty: 'children',
74269
74270     /**
74271      * @cfg {Boolean} folderSort Set to true to automatically prepend a leaf sorter (defaults to <tt>undefined</tt>)
74272      */
74273     folderSort: false,
74274     
74275     constructor: function(config) {
74276         var me = this, 
74277             root,
74278             fields;
74279             
74280         
74281         config = Ext.apply({}, config);
74282         
74283         /**
74284          * If we have no fields declare for the store, add some defaults.
74285          * These will be ignored if a model is explicitly specified.
74286          */
74287         fields = config.fields || me.fields;
74288         if (!fields) {
74289             config.fields = [{name: 'text', type: 'string'}];
74290         }
74291
74292         me.callParent([config]);
74293         
74294         // We create our data tree.
74295         me.tree = Ext.create('Ext.data.Tree');
74296         
74297         me.tree.on({
74298             scope: me,
74299             remove: me.onNodeRemove,
74300             beforeexpand: me.onBeforeNodeExpand,
74301             beforecollapse: me.onBeforeNodeCollapse,
74302             append: me.onNodeAdded,
74303             insert: me.onNodeAdded
74304         });
74305
74306         me.onBeforeSort();
74307                 
74308         root = me.root;
74309         if (root) {
74310             delete me.root;
74311             me.setRootNode(root);            
74312         }
74313
74314         me.relayEvents(me.tree, [
74315             /**
74316              * @event append
74317              * Fires when a new child node is appended to a node in this store's tree.
74318              * @param {Tree} tree The owner tree
74319              * @param {Node} parent The parent node
74320              * @param {Node} node The newly appended node
74321              * @param {Number} index The index of the newly appended node
74322              */
74323             "append",
74324             
74325             /**
74326              * @event remove
74327              * Fires when a child node is removed from a node in this store's tree.
74328              * @param {Tree} tree The owner tree
74329              * @param {Node} parent The parent node
74330              * @param {Node} node The child node removed
74331              */
74332             "remove",
74333             
74334             /**
74335              * @event move
74336              * Fires when a node is moved to a new location in the store's tree
74337              * @param {Tree} tree The owner tree
74338              * @param {Node} node The node moved
74339              * @param {Node} oldParent The old parent of this node
74340              * @param {Node} newParent The new parent of this node
74341              * @param {Number} index The index it was moved to
74342              */
74343             "move",
74344             
74345             /**
74346              * @event insert
74347              * Fires when a new child node is inserted in a node in this store's tree.
74348              * @param {Tree} tree The owner tree
74349              * @param {Node} parent The parent node
74350              * @param {Node} node The child node inserted
74351              * @param {Node} refNode The child node the node was inserted before
74352              */
74353             "insert",
74354             
74355             /**
74356              * @event beforeappend
74357              * Fires before a new child is appended to a node in this store's tree, return false to cancel the append.
74358              * @param {Tree} tree The owner tree
74359              * @param {Node} parent The parent node
74360              * @param {Node} node The child node to be appended
74361              */
74362             "beforeappend",
74363             
74364             /**
74365              * @event beforeremove
74366              * Fires before a child is removed from a node in this store's tree, return false to cancel the remove.
74367              * @param {Tree} tree The owner tree
74368              * @param {Node} parent The parent node
74369              * @param {Node} node The child node to be removed
74370              */
74371             "beforeremove",
74372             
74373             /**
74374              * @event beforemove
74375              * Fires before a node is moved to a new location in the store's tree. Return false to cancel the move.
74376              * @param {Tree} tree The owner tree
74377              * @param {Node} node The node being moved
74378              * @param {Node} oldParent The parent of the node
74379              * @param {Node} newParent The new parent the node is moving to
74380              * @param {Number} index The index it is being moved to
74381              */
74382             "beforemove",
74383             
74384             /**
74385              * @event beforeinsert
74386              * Fires before a new child is inserted in a node in this store's tree, return false to cancel the insert.
74387              * @param {Tree} tree The owner tree
74388              * @param {Node} parent The parent node
74389              * @param {Node} node The child node to be inserted
74390              * @param {Node} refNode The child node the node is being inserted before
74391              */
74392             "beforeinsert",
74393              
74394              /**
74395               * @event expand
74396               * Fires when this node is expanded.
74397               * @param {Node} this The expanding node
74398               */
74399              "expand",
74400              
74401              /**
74402               * @event collapse
74403               * Fires when this node is collapsed.
74404               * @param {Node} this The collapsing node
74405               */
74406              "collapse",
74407              
74408              /**
74409               * @event beforeexpand
74410               * Fires before this node is expanded.
74411               * @param {Node} this The expanding node
74412               */
74413              "beforeexpand",
74414              
74415              /**
74416               * @event beforecollapse
74417               * Fires before this node is collapsed.
74418               * @param {Node} this The collapsing node
74419               */
74420              "beforecollapse",
74421
74422              /**
74423               * @event sort
74424               * Fires when this TreeStore is sorted.
74425               * @param {Node} node The node that is sorted.
74426               */             
74427              "sort",
74428              
74429              /**
74430               * @event rootchange
74431               * Fires whenever the root node is changed in the tree.
74432               * @param {Ext.data.Model} root The new root
74433               */
74434              "rootchange"
74435         ]);
74436         
74437         me.addEvents(
74438             /**
74439              * @event rootchange
74440              * Fires when the root node on this TreeStore is changed.
74441              * @param {Ext.data.TreeStore} store This TreeStore
74442              * @param {Node} The new root node.
74443              */
74444             'rootchange'
74445         );
74446         
74447         if (Ext.isDefined(me.nodeParameter)) {
74448             if (Ext.isDefined(Ext.global.console)) {
74449                 Ext.global.console.warn('Ext.data.TreeStore: nodeParameter has been deprecated. Please use nodeParam instead.');
74450             }
74451             me.nodeParam = me.nodeParameter;
74452             delete me.nodeParameter;
74453         }
74454     },
74455     
74456     // inherit docs
74457     setProxy: function(proxy) {
74458         var reader,
74459             needsRoot;
74460         
74461         if (proxy instanceof Ext.data.proxy.Proxy) {
74462             // proxy instance, check if a root was set
74463             needsRoot = Ext.isEmpty(proxy.getReader().root);
74464         } else if (Ext.isString(proxy)) {
74465             // string type, means a reader can't be set
74466             needsRoot = true;
74467         } else {
74468             // object, check if a reader and a root were specified.
74469             reader = proxy.reader;
74470             needsRoot = !(reader && !Ext.isEmpty(reader.root));
74471         }
74472         proxy = this.callParent(arguments);
74473         if (needsRoot) {
74474             reader = proxy.getReader();
74475             reader.root = this.defaultRootProperty;
74476             // force rebuild
74477             reader.buildExtractors(true);
74478         }
74479     },
74480     
74481     // inherit docs
74482     onBeforeSort: function() {
74483         if (this.folderSort) {
74484             this.sort({
74485                 property: 'leaf',
74486                 direction: 'ASC'
74487             }, 'prepend', false);    
74488         }
74489     },
74490     
74491     /**
74492      * Called before a node is expanded.
74493      * @private
74494      * @param {Ext.data.NodeInterface} node The node being expanded.
74495      * @param {Function} callback The function to run after the expand finishes
74496      * @param {Object} scope The scope in which to run the callback function
74497      */
74498     onBeforeNodeExpand: function(node, callback, scope) {
74499         if (node.isLoaded()) {
74500             Ext.callback(callback, scope || node, [node.childNodes]);
74501         }
74502         else if (node.isLoading()) {
74503             this.on('load', function() {
74504                 Ext.callback(callback, scope || node, [node.childNodes]);
74505             }, this, {single: true});
74506         }
74507         else {
74508             this.read({
74509                 node: node,
74510                 callback: function() {
74511                     Ext.callback(callback, scope || node, [node.childNodes]);
74512                 }
74513             });            
74514         }
74515     },
74516     
74517     //inherit docs
74518     getNewRecords: function() {
74519         return Ext.Array.filter(this.tree.flatten(), this.filterNew);
74520     },
74521
74522     //inherit docs
74523     getUpdatedRecords: function() {
74524         return Ext.Array.filter(this.tree.flatten(), this.filterUpdated);
74525     },
74526     
74527     /**
74528      * Called before a node is collapsed.
74529      * @private
74530      * @param {Ext.data.NodeInterface} node The node being collapsed.
74531      * @param {Function} callback The function to run after the collapse finishes
74532      * @param {Object} scope The scope in which to run the callback function
74533      */
74534     onBeforeNodeCollapse: function(node, callback, scope) {
74535         callback.call(scope || node, node.childNodes);
74536     },
74537     
74538     onNodeRemove: function(parent, node) {
74539         var removed = this.removed;
74540         
74541         if (!node.isReplace && Ext.Array.indexOf(removed, node) == -1) {
74542             removed.push(node);
74543         }
74544     },
74545     
74546     onNodeAdded: function(parent, node) {
74547         var proxy = this.getProxy(),
74548             reader = proxy.getReader(),
74549             data = node.raw || node.data,
74550             dataRoot, children;
74551             
74552         Ext.Array.remove(this.removed, node); 
74553         
74554         if (!node.isLeaf() && !node.isLoaded()) {
74555             dataRoot = reader.getRoot(data);
74556             if (dataRoot) {
74557                 this.fillNode(node, reader.extractData(dataRoot));
74558                 delete data[reader.root];
74559             }
74560         }
74561     },
74562         
74563     /**
74564      * Sets the root node for this store
74565      * @param {Ext.data.Model/Ext.data.NodeInterface} root
74566      * @return {Ext.data.NodeInterface} The new root
74567      */
74568     setRootNode: function(root) {
74569         var me = this;
74570
74571         root = root || {};        
74572         if (!root.isNode) {
74573             // create a default rootNode and create internal data struct.        
74574             Ext.applyIf(root, {
74575                 id: me.defaultRootId,
74576                 text: 'Root',
74577                 allowDrag: false
74578             });
74579             root = Ext.ModelManager.create(root, me.model);
74580         }
74581         Ext.data.NodeInterface.decorate(root);
74582
74583         // Because we have decorated the model with new fields,
74584         // we need to build new extactor functions on the reader.
74585         me.getProxy().getReader().buildExtractors(true);
74586         
74587         // When we add the root to the tree, it will automaticaly get the NodeInterface
74588         me.tree.setRootNode(root);
74589         
74590         // If the user has set expanded: true on the root, we want to call the expand function
74591         if (!root.isLoaded() && root.isExpanded()) {
74592             me.load({
74593                 node: root
74594             });
74595         }
74596         
74597         return root;
74598     },
74599         
74600     /**
74601      * Returns the root node for this tree.
74602      * @return {Ext.data.NodeInterface}
74603      */
74604     getRootNode: function() {
74605         return this.tree.getRootNode();
74606     },
74607
74608     /**
74609      * Returns the record node by id
74610      * @return {Ext.data.NodeInterface}
74611      */
74612     getNodeById: function(id) {
74613         return this.tree.getNodeById(id);
74614     },
74615
74616     /**
74617      * Loads the Store using its configured {@link #proxy}.
74618      * @param {Object} options Optional config object. This is passed into the {@link Ext.data.Operation Operation}
74619      * object that is created and then sent to the proxy's {@link Ext.data.proxy.Proxy#read} function.
74620      * The options can also contain a node, which indicates which node is to be loaded. If not specified, it will
74621      * default to the root node.
74622      */
74623     load: function(options) {
74624         options = options || {};
74625         options.params = options.params || {};
74626         
74627         var me = this,
74628             node = options.node || me.tree.getRootNode(),
74629             root;
74630             
74631         // If there is not a node it means the user hasnt defined a rootnode yet. In this case lets just
74632         // create one for them.
74633         if (!node) {
74634             node = me.setRootNode({
74635                 expanded: true
74636             });
74637         }
74638         
74639         if (me.clearOnLoad) {
74640             node.removeAll();
74641         }
74642         
74643         Ext.applyIf(options, {
74644             node: node
74645         });
74646         options.params[me.nodeParam] = node ? node.getId() : 'root';
74647         
74648         if (node) {
74649             node.set('loading', true);
74650         }
74651         
74652         return me.callParent([options]);
74653     },
74654         
74655
74656     /**
74657      * Fills a node with a series of child records.
74658      * @private
74659      * @param {Ext.data.NodeInterface} node The node to fill
74660      * @param {Array} records The records to add
74661      */
74662     fillNode: function(node, records) {
74663         var me = this,
74664             ln = records ? records.length : 0,
74665             i = 0, sortCollection;
74666
74667         if (ln && me.sortOnLoad && !me.remoteSort && me.sorters && me.sorters.items) {
74668             sortCollection = Ext.create('Ext.util.MixedCollection');
74669             sortCollection.addAll(records);
74670             sortCollection.sort(me.sorters.items);
74671             records = sortCollection.items;
74672         }
74673         
74674         node.set('loaded', true);
74675         for (; i < ln; i++) {
74676             node.appendChild(records[i], undefined, true);
74677         }
74678         
74679         return records;
74680     },
74681
74682     // inherit docs
74683     onProxyLoad: function(operation) {
74684         var me = this,
74685             successful = operation.wasSuccessful(),
74686             records = operation.getRecords(),
74687             node = operation.node;
74688
74689         node.set('loading', false);
74690         if (successful) {
74691             records = me.fillNode(node, records);
74692         }
74693         // deprecate read?
74694         me.fireEvent('read', me, operation.node, records, successful);
74695         me.fireEvent('load', me, operation.node, records, successful);
74696         //this is a callback that would have been passed to the 'read' function and is optional
74697         Ext.callback(operation.callback, operation.scope || me, [records, operation, successful]);
74698     },
74699     
74700     /**
74701      * Create any new records when a write is returned from the server.
74702      * @private
74703      * @param {Array} records The array of new records
74704      * @param {Ext.data.Operation} operation The operation that just completed
74705      * @param {Boolean} success True if the operation was successful
74706      */
74707     onCreateRecords: function(records, operation, success) {
74708         if (success) {
74709             var i = 0,
74710                 length = records.length,
74711                 originalRecords = operation.records,
74712                 parentNode,
74713                 record,
74714                 original,
74715                 index;
74716
74717             /**
74718              * Loop over each record returned from the server. Assume they are
74719              * returned in order of how they were sent. If we find a matching
74720              * record, replace it with the newly created one.
74721              */
74722             for (; i < length; ++i) {
74723                 record = records[i];
74724                 original = originalRecords[i];
74725                 if (original) {
74726                     parentNode = original.parentNode;
74727                     if (parentNode) {
74728                         // prevent being added to the removed cache
74729                         original.isReplace = true;
74730                         parentNode.replaceChild(record, original);
74731                         delete original.isReplace;
74732                     }
74733                     record.phantom = false;
74734                 }
74735             }
74736         }
74737     },
74738
74739     /**
74740      * Update any records when a write is returned from the server.
74741      * @private
74742      * @param {Array} records The array of updated records
74743      * @param {Ext.data.Operation} operation The operation that just completed
74744      * @param {Boolean} success True if the operation was successful
74745      */
74746     onUpdateRecords: function(records, operation, success){
74747         if (success) {
74748             var me = this,
74749                 i = 0,
74750                 length = records.length,
74751                 data = me.data,
74752                 original,
74753                 parentNode,
74754                 record;
74755
74756             for (; i < length; ++i) {
74757                 record = records[i];
74758                 original = me.tree.getNodeById(record.getId());
74759                 parentNode = original.parentNode;
74760                 if (parentNode) {
74761                     // prevent being added to the removed cache
74762                     original.isReplace = true;
74763                     parentNode.replaceChild(record, original);
74764                     original.isReplace = false;
74765                 }
74766             }
74767         }
74768     },
74769
74770     /**
74771      * Remove any records when a write is returned from the server.
74772      * @private
74773      * @param {Array} records The array of removed records
74774      * @param {Ext.data.Operation} operation The operation that just completed
74775      * @param {Boolean} success True if the operation was successful
74776      */
74777     onDestroyRecords: function(records, operation, success){
74778         if (success) {
74779             this.removed = [];
74780         }
74781     },
74782
74783     // inherit docs
74784     removeAll: function() {
74785         this.getRootNode().destroy();
74786         this.fireEvent('clear', this);
74787     },
74788
74789     // inherit docs
74790     doSort: function(sorterFn) {
74791         var me = this;
74792         if (me.remoteSort) {
74793             //the load function will pick up the new sorters and request the sorted data from the proxy
74794             me.load();
74795         } else {
74796             me.tree.sort(sorterFn, true);
74797             me.fireEvent('datachanged', me);
74798         }   
74799         me.fireEvent('sort', me);
74800     }
74801 });
74802 /**
74803  * @author Ed Spencer
74804  * @class Ext.data.XmlStore
74805  * @extends Ext.data.Store
74806  * @private
74807  * @ignore
74808  * <p>Small helper class to make creating {@link Ext.data.Store}s from XML data easier.
74809  * A XmlStore will be automatically configured with a {@link Ext.data.reader.Xml}.</p>
74810  * <p>A store configuration would be something like:<pre><code>
74811 var store = new Ext.data.XmlStore({
74812     // store configs
74813     autoDestroy: true,
74814     storeId: 'myStore',
74815     url: 'sheldon.xml', // automatically configures a HttpProxy
74816     // reader configs
74817     record: 'Item', // records will have an "Item" tag
74818     idPath: 'ASIN',
74819     totalRecords: '@TotalResults'
74820     fields: [
74821         // set up the fields mapping into the xml doc
74822         // The first needs mapping, the others are very basic
74823         {name: 'Author', mapping: 'ItemAttributes > Author'},
74824         'Title', 'Manufacturer', 'ProductGroup'
74825     ]
74826 });
74827  * </code></pre></p>
74828  * <p>This store is configured to consume a returned object of the form:<pre><code>
74829 &#60?xml version="1.0" encoding="UTF-8"?>
74830 &#60ItemSearchResponse xmlns="http://webservices.amazon.com/AWSECommerceService/2009-05-15">
74831     &#60Items>
74832         &#60Request>
74833             &#60IsValid>True&#60/IsValid>
74834             &#60ItemSearchRequest>
74835                 &#60Author>Sidney Sheldon&#60/Author>
74836                 &#60SearchIndex>Books&#60/SearchIndex>
74837             &#60/ItemSearchRequest>
74838         &#60/Request>
74839         &#60TotalResults>203&#60/TotalResults>
74840         &#60TotalPages>21&#60/TotalPages>
74841         &#60Item>
74842             &#60ASIN>0446355453&#60/ASIN>
74843             &#60DetailPageURL>
74844                 http://www.amazon.com/
74845             &#60/DetailPageURL>
74846             &#60ItemAttributes>
74847                 &#60Author>Sidney Sheldon&#60/Author>
74848                 &#60Manufacturer>Warner Books&#60/Manufacturer>
74849                 &#60ProductGroup>Book&#60/ProductGroup>
74850                 &#60Title>Master of the Game&#60/Title>
74851             &#60/ItemAttributes>
74852         &#60/Item>
74853     &#60/Items>
74854 &#60/ItemSearchResponse>
74855  * </code></pre>
74856  * An object literal of this form could also be used as the {@link #data} config option.</p>
74857  * <p><b>Note:</b> Although not listed here, this class accepts all of the configuration options of
74858  * <b>{@link Ext.data.reader.Xml XmlReader}</b>.</p>
74859  * @constructor
74860  * @param {Object} config
74861  * @xtype xmlstore
74862  */
74863 Ext.define('Ext.data.XmlStore', {
74864     extend: 'Ext.data.Store',
74865     alternateClassName: 'Ext.data.XmlStore',
74866     alias: 'store.xml',
74867
74868     /**
74869      * @cfg {Ext.data.DataReader} reader @hide
74870      */
74871     constructor: function(config){
74872         config = config || {};
74873         config = config || {};
74874
74875         Ext.applyIf(config, {
74876             proxy: {
74877                 type: 'ajax',
74878                 reader: 'xml',
74879                 writer: 'xml'
74880             }
74881         });
74882
74883         this.callParent([config]);
74884     }
74885 });
74886
74887 /**
74888  * @author Ed Spencer
74889  * @class Ext.data.proxy.Client
74890  * @extends Ext.data.proxy.Proxy
74891  * 
74892  * <p>Base class for any client-side storage. Used as a superclass for {@link Ext.data.proxy.Memory Memory} and 
74893  * {@link Ext.data.proxy.WebStorage Web Storage} proxies. Do not use directly, use one of the subclasses instead.</p>
74894  */
74895 Ext.define('Ext.data.proxy.Client', {
74896     extend: 'Ext.data.proxy.Proxy',
74897     alternateClassName: 'Ext.data.ClientProxy',
74898     
74899     /**
74900      * Abstract function that must be implemented by each ClientProxy subclass. This should purge all record data
74901      * from the client side storage, as well as removing any supporting data (such as lists of record IDs)
74902      */
74903     clear: function() {
74904         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.");
74905     }
74906 });
74907 /**
74908  * @author Ed Spencer
74909  * @class Ext.data.proxy.JsonP
74910  * @extends Ext.data.proxy.Server
74911  *
74912  * <p>JsonPProxy is useful when you need to load data from a domain other than the one your application is running
74913  * on. If your application is running on http://domainA.com it cannot use {@link Ext.data.proxy.Ajax Ajax} to load its
74914  * data from http://domainB.com because cross-domain ajax requests are prohibited by the browser.</p>
74915  *
74916  * <p>We can get around this using a JsonPProxy. JsonPProxy injects a &lt;script&gt; tag into the DOM whenever
74917  * an AJAX request would usually be made. Let's say we want to load data from http://domainB.com/users - the script tag
74918  * that would be injected might look like this:</p>
74919  *
74920 <pre><code>
74921 &lt;script src="http://domainB.com/users?callback=someCallback"&gt;&lt;/script&gt;
74922 </code></pre>
74923  *
74924  * <p>When we inject the tag above, the browser makes a request to that url and includes the response as if it was any
74925  * other type of JavaScript include. By passing a callback in the url above, we're telling domainB's server that we
74926  * want to be notified when the result comes in and that it should call our callback function with the data it sends
74927  * back. So long as the server formats the response to look like this, everything will work:</p>
74928  *
74929 <pre><code>
74930 someCallback({
74931     users: [
74932         {
74933             id: 1,
74934             name: "Ed Spencer",
74935             email: "ed@sencha.com"
74936         }
74937     ]
74938 });
74939 </code></pre>
74940  *
74941  * <p>As soon as the script finishes loading, the 'someCallback' function that we passed in the url is called with the
74942  * JSON object that the server returned.</p>
74943  *
74944  * <p>JsonPProxy takes care of all of this automatically. It formats the url you pass, adding the callback
74945  * parameter automatically. It even creates a temporary callback function, waits for it to be called and then puts
74946  * the data into the Proxy making it look just like you loaded it through a normal {@link Ext.data.proxy.Ajax AjaxProxy}.
74947  * Here's how we might set that up:</p>
74948  *
74949 <pre><code>
74950 Ext.define('User', {
74951     extend: 'Ext.data.Model',
74952     fields: ['id', 'name', 'email']
74953 });
74954
74955 var store = new Ext.data.Store({
74956     model: 'User',
74957     proxy: {
74958         type: 'jsonp',
74959         url : 'http://domainB.com/users'
74960     }
74961 });
74962
74963 store.load();
74964 </code></pre>
74965  *
74966  * <p>That's all we need to do - JsonPProxy takes care of the rest. In this case the Proxy will have injected a
74967  * script tag like this:
74968  *
74969 <pre><code>
74970 &lt;script src="http://domainB.com/users?callback=stcCallback001" id="stcScript001"&gt;&lt;/script&gt;
74971 </code></pre>
74972  *
74973  * <p><u>Customization</u></p>
74974  *
74975  * <p>Most parts of this script tag can be customized using the {@link #callbackParam}, {@link #callbackPrefix} and
74976  * {@link #scriptIdPrefix} configurations. For example:
74977  *
74978 <pre><code>
74979 var store = new Ext.data.Store({
74980     model: 'User',
74981     proxy: {
74982         type: 'jsonp',
74983         url : 'http://domainB.com/users',
74984         callbackParam: 'theCallbackFunction',
74985         callbackPrefix: 'ABC',
74986         scriptIdPrefix: 'injectedScript'
74987     }
74988 });
74989
74990 store.load();
74991 </code></pre>
74992  *
74993  * <p>Would inject a script tag like this:</p>
74994  *
74995 <pre><code>
74996 &lt;script src="http://domainB.com/users?theCallbackFunction=ABC001" id="injectedScript001"&gt;&lt;/script&gt;
74997 </code></pre>
74998  *
74999  * <p><u>Implementing on the server side</u></p>
75000  *
75001  * <p>The remote server side needs to be configured to return data in this format. Here are suggestions for how you
75002  * might achieve this using Java, PHP and ASP.net:</p>
75003  *
75004  * <p>Java:</p>
75005  *
75006 <pre><code>
75007 boolean jsonP = false;
75008 String cb = request.getParameter("callback");
75009 if (cb != null) {
75010     jsonP = true;
75011     response.setContentType("text/javascript");
75012 } else {
75013     response.setContentType("application/x-json");
75014 }
75015 Writer out = response.getWriter();
75016 if (jsonP) {
75017     out.write(cb + "(");
75018 }
75019 out.print(dataBlock.toJsonString());
75020 if (jsonP) {
75021     out.write(");");
75022 }
75023 </code></pre>
75024  *
75025  * <p>PHP:</p>
75026  *
75027 <pre><code>
75028 $callback = $_REQUEST['callback'];
75029
75030 // Create the output object.
75031 $output = array('a' => 'Apple', 'b' => 'Banana');
75032
75033 //start output
75034 if ($callback) {
75035     header('Content-Type: text/javascript');
75036     echo $callback . '(' . json_encode($output) . ');';
75037 } else {
75038     header('Content-Type: application/x-json');
75039     echo json_encode($output);
75040 }
75041 </code></pre>
75042  *
75043  * <p>ASP.net:</p>
75044  *
75045 <pre><code>
75046 String jsonString = "{success: true}";
75047 String cb = Request.Params.Get("callback");
75048 String responseString = "";
75049 if (!String.IsNullOrEmpty(cb)) {
75050     responseString = cb + "(" + jsonString + ")";
75051 } else {
75052     responseString = jsonString;
75053 }
75054 Response.Write(responseString);
75055 </code></pre>
75056  *
75057  */
75058 Ext.define('Ext.data.proxy.JsonP', {
75059     extend: 'Ext.data.proxy.Server',
75060     alternateClassName: 'Ext.data.ScriptTagProxy',
75061     alias: ['proxy.jsonp', 'proxy.scripttag'],
75062     requires: ['Ext.data.JsonP'],
75063
75064     defaultWriterType: 'base',
75065
75066     /**
75067      * @cfg {String} callbackKey (Optional) See {@link Ext.data.JsonP#callbackKey}.
75068      */
75069     callbackKey : 'callback',
75070
75071     /**
75072      * @cfg {String} recordParam
75073      * The param name to use when passing records to the server (e.g. 'records=someEncodedRecordString').
75074      * Defaults to 'records'
75075      */
75076     recordParam: 'records',
75077
75078     /**
75079      * @cfg {Boolean} autoAppendParams True to automatically append the request's params to the generated url. Defaults to true
75080      */
75081     autoAppendParams: true,
75082
75083     constructor: function(){
75084         this.addEvents(
75085             /**
75086              * @event exception
75087              * Fires when the server returns an exception
75088              * @param {Ext.data.proxy.Proxy} this
75089              * @param {Ext.data.Request} request The request that was sent
75090              * @param {Ext.data.Operation} operation The operation that triggered the request
75091              */
75092             'exception'
75093         );
75094         this.callParent(arguments);
75095     },
75096
75097     /**
75098      * @private
75099      * Performs the read request to the remote domain. JsonPProxy does not actually create an Ajax request,
75100      * instead we write out a <script> tag based on the configuration of the internal Ext.data.Request object
75101      * @param {Ext.data.Operation} operation The {@link Ext.data.Operation Operation} object to execute
75102      * @param {Function} callback A callback function to execute when the Operation has been completed
75103      * @param {Object} scope The scope to execute the callback in
75104      */
75105     doRequest: function(operation, callback, scope) {
75106         //generate the unique IDs for this request
75107         var me      = this,
75108             writer  = me.getWriter(),
75109             request = me.buildRequest(operation),
75110             params = request.params;
75111
75112         if (operation.allowWrite()) {
75113             request = writer.write(request);
75114         }
75115
75116         //apply JsonPProxy-specific attributes to the Request
75117         Ext.apply(request, {
75118             callbackKey: me.callbackKey,
75119             timeout: me.timeout,
75120             scope: me,
75121             disableCaching: false, // handled by the proxy
75122             callback: me.createRequestCallback(request, operation, callback, scope)
75123         });
75124         
75125         // prevent doubling up
75126         if (me.autoAppendParams) {
75127             request.params = {};
75128         }
75129         
75130         request.jsonp = Ext.data.JsonP.request(request);
75131         // restore on the request
75132         request.params = params;
75133         operation.setStarted();
75134         me.lastRequest = request;
75135
75136         return request;
75137     },
75138
75139     /**
75140      * @private
75141      * Creates and returns the function that is called when the request has completed. The returned function
75142      * should accept a Response object, which contains the response to be read by the configured Reader.
75143      * The third argument is the callback that should be called after the request has been completed and the Reader has decoded
75144      * the response. This callback will typically be the callback passed by a store, e.g. in proxy.read(operation, theCallback, scope)
75145      * theCallback refers to the callback argument received by this function.
75146      * See {@link #doRequest} for details.
75147      * @param {Ext.data.Request} request The Request object
75148      * @param {Ext.data.Operation} operation The Operation being executed
75149      * @param {Function} callback The callback function to be called when the request completes. This is usually the callback
75150      * passed to doRequest
75151      * @param {Object} scope The scope in which to execute the callback function
75152      * @return {Function} The callback function
75153      */
75154     createRequestCallback: function(request, operation, callback, scope) {
75155         var me = this;
75156
75157         return function(success, response, errorType) {
75158             delete me.lastRequest;
75159             me.processResponse(success, operation, request, response, callback, scope);
75160         };
75161     },
75162     
75163     // inherit docs
75164     setException: function(operation, response) {
75165         operation.setException(operation.request.jsonp.errorType);
75166     },
75167
75168
75169     /**
75170      * Generates a url based on a given Ext.data.Request object. Adds the params and callback function name to the url
75171      * @param {Ext.data.Request} request The request object
75172      * @return {String} The url
75173      */
75174     buildUrl: function(request) {
75175         var me      = this,
75176             url     = me.callParent(arguments),
75177             params  = Ext.apply({}, request.params),
75178             filters = params.filters,
75179             records,
75180             filter, i;
75181
75182         delete params.filters;
75183  
75184         if (me.autoAppendParams) {
75185             url = Ext.urlAppend(url, Ext.Object.toQueryString(params));
75186         }
75187
75188         if (filters && filters.length) {
75189             for (i = 0; i < filters.length; i++) {
75190                 filter = filters[i];
75191
75192                 if (filter.value) {
75193                     url = Ext.urlAppend(url, filter.property + "=" + filter.value);
75194                 }
75195             }
75196         }
75197
75198         //if there are any records present, append them to the url also
75199         records = request.records;
75200
75201         if (Ext.isArray(records) && records.length > 0) {
75202             url = Ext.urlAppend(url, Ext.String.format("{0}={1}", me.recordParam, me.encodeRecords(records)));
75203         }
75204
75205         return url;
75206     },
75207
75208     //inherit docs
75209     destroy: function() {
75210         this.abort();
75211         this.callParent();
75212     },
75213
75214     /**
75215      * Aborts the current server request if one is currently running
75216      */
75217     abort: function() {
75218         var lastRequest = this.lastRequest;
75219         if (lastRequest) {
75220             Ext.data.JsonP.abort(lastRequest.jsonp);
75221         }
75222     },
75223
75224     /**
75225      * Encodes an array of records into a string suitable to be appended to the script src url. This is broken
75226      * out into its own function so that it can be easily overridden.
75227      * @param {Array} records The records array
75228      * @return {String} The encoded records string
75229      */
75230     encodeRecords: function(records) {
75231         var encoded = "",
75232             i = 0,
75233             len = records.length;
75234
75235         for (; i < len; i++) {
75236             encoded += Ext.Object.toQueryString(records[i].data);
75237         }
75238
75239         return encoded;
75240     }
75241 });
75242
75243 /**
75244  * @author Ed Spencer
75245  * @class Ext.data.proxy.WebStorage
75246  * @extends Ext.data.proxy.Client
75247  * 
75248  * <p>WebStorageProxy is simply a superclass for the {@link Ext.data.proxy.LocalStorage localStorage} and 
75249  * {@link Ext.data.proxy.SessionStorage sessionStorage} proxies. It uses the new HTML5 key/value client-side storage 
75250  * objects to save {@link Ext.data.Model model instances} for offline use.</p>
75251  * 
75252  * @constructor
75253  * Creates the proxy, throws an error if local storage is not supported in the current browser
75254  * @param {Object} config Optional config object
75255  */
75256 Ext.define('Ext.data.proxy.WebStorage', {
75257     extend: 'Ext.data.proxy.Client',
75258     alternateClassName: 'Ext.data.WebStorageProxy',
75259     
75260     /**
75261      * @cfg {String} id The unique ID used as the key in which all record data are stored in the local storage object
75262      */
75263     id: undefined,
75264
75265     /**
75266      * @ignore
75267      */
75268     constructor: function(config) {
75269         this.callParent(arguments);
75270         
75271         /**
75272          * Cached map of records already retrieved by this Proxy - ensures that the same instance is always retrieved
75273          * @property cache
75274          * @type Object
75275          */
75276         this.cache = {};
75277
75278         if (this.getStorageObject() === undefined) {
75279             Ext.Error.raise("Local Storage is not supported in this browser, please use another type of data proxy");
75280         }
75281
75282         //if an id is not given, try to use the store's id instead
75283         this.id = this.id || (this.store ? this.store.storeId : undefined);
75284
75285         if (this.id === undefined) {
75286             Ext.Error.raise("No unique id was provided to the local storage proxy. See Ext.data.proxy.LocalStorage documentation for details");
75287         }
75288
75289         this.initialize();
75290     },
75291
75292     //inherit docs
75293     create: function(operation, callback, scope) {
75294         var records = operation.records,
75295             length  = records.length,
75296             ids     = this.getIds(),
75297             id, record, i;
75298         
75299         operation.setStarted();
75300
75301         for (i = 0; i < length; i++) {
75302             record = records[i];
75303
75304             if (record.phantom) {
75305                 record.phantom = false;
75306                 id = this.getNextId();
75307             } else {
75308                 id = record.getId();
75309             }
75310
75311             this.setRecord(record, id);
75312             ids.push(id);
75313         }
75314
75315         this.setIds(ids);
75316
75317         operation.setCompleted();
75318         operation.setSuccessful();
75319
75320         if (typeof callback == 'function') {
75321             callback.call(scope || this, operation);
75322         }
75323     },
75324
75325     //inherit docs
75326     read: function(operation, callback, scope) {
75327         //TODO: respect sorters, filters, start and limit options on the Operation
75328
75329         var records = [],
75330             ids     = this.getIds(),
75331             length  = ids.length,
75332             i, recordData, record;
75333         
75334         //read a single record
75335         if (operation.id) {
75336             record = this.getRecord(operation.id);
75337             
75338             if (record) {
75339                 records.push(record);
75340                 operation.setSuccessful();
75341             }
75342         } else {
75343             for (i = 0; i < length; i++) {
75344                 records.push(this.getRecord(ids[i]));
75345             }
75346             operation.setSuccessful();
75347         }
75348         
75349         operation.setCompleted();
75350
75351         operation.resultSet = Ext.create('Ext.data.ResultSet', {
75352             records: records,
75353             total  : records.length,
75354             loaded : true
75355         });
75356
75357         if (typeof callback == 'function') {
75358             callback.call(scope || this, operation);
75359         }
75360     },
75361
75362     //inherit docs
75363     update: function(operation, callback, scope) {
75364         var records = operation.records,
75365             length  = records.length,
75366             ids     = this.getIds(),
75367             record, id, i;
75368
75369         operation.setStarted();
75370
75371         for (i = 0; i < length; i++) {
75372             record = records[i];
75373             this.setRecord(record);
75374             
75375             //we need to update the set of ids here because it's possible that a non-phantom record was added
75376             //to this proxy - in which case the record's id would never have been added via the normal 'create' call
75377             id = record.getId();
75378             if (id !== undefined && Ext.Array.indexOf(ids, id) == -1) {
75379                 ids.push(id);
75380             }
75381         }
75382         this.setIds(ids);
75383
75384         operation.setCompleted();
75385         operation.setSuccessful();
75386
75387         if (typeof callback == 'function') {
75388             callback.call(scope || this, operation);
75389         }
75390     },
75391
75392     //inherit
75393     destroy: function(operation, callback, scope) {
75394         var records = operation.records,
75395             length  = records.length,
75396             ids     = this.getIds(),
75397
75398             //newIds is a copy of ids, from which we remove the destroyed records
75399             newIds  = [].concat(ids),
75400             i;
75401
75402         for (i = 0; i < length; i++) {
75403             Ext.Array.remove(newIds, records[i].getId());
75404             this.removeRecord(records[i], false);
75405         }
75406
75407         this.setIds(newIds);
75408         
75409         operation.setCompleted();
75410         operation.setSuccessful();
75411
75412         if (typeof callback == 'function') {
75413             callback.call(scope || this, operation);
75414         }
75415     },
75416
75417     /**
75418      * @private
75419      * Fetches a model instance from the Proxy by ID. Runs each field's decode function (if present) to decode the data
75420      * @param {String} id The record's unique ID
75421      * @return {Ext.data.Model} The model instance
75422      */
75423     getRecord: function(id) {
75424         if (this.cache[id] === undefined) {
75425             var rawData = Ext.decode(this.getStorageObject().getItem(this.getRecordKey(id))),
75426                 data    = {},
75427                 Model   = this.model,
75428                 fields  = Model.prototype.fields.items,
75429                 length  = fields.length,
75430                 i, field, name, record;
75431
75432             for (i = 0; i < length; i++) {
75433                 field = fields[i];
75434                 name  = field.name;
75435
75436                 if (typeof field.decode == 'function') {
75437                     data[name] = field.decode(rawData[name]);
75438                 } else {
75439                     data[name] = rawData[name];
75440                 }
75441             }
75442
75443             record = new Model(data, id);
75444             record.phantom = false;
75445
75446             this.cache[id] = record;
75447         }
75448         
75449         return this.cache[id];
75450     },
75451
75452     /**
75453      * Saves the given record in the Proxy. Runs each field's encode function (if present) to encode the data
75454      * @param {Ext.data.Model} record The model instance
75455      * @param {String} id The id to save the record under (defaults to the value of the record's getId() function)
75456      */
75457     setRecord: function(record, id) {
75458         if (id) {
75459             record.setId(id);
75460         } else {
75461             id = record.getId();
75462         }
75463
75464         var me = this,
75465             rawData = record.data,
75466             data    = {},
75467             model   = me.model,
75468             fields  = model.prototype.fields.items,
75469             length  = fields.length,
75470             i = 0,
75471             field, name, obj, key;
75472
75473         for (; i < length; i++) {
75474             field = fields[i];
75475             name  = field.name;
75476
75477             if (typeof field.encode == 'function') {
75478                 data[name] = field.encode(rawData[name], record);
75479             } else {
75480                 data[name] = rawData[name];
75481             }
75482         }
75483
75484         obj = me.getStorageObject();
75485         key = me.getRecordKey(id);
75486         
75487         //keep the cache up to date
75488         me.cache[id] = record;
75489         
75490         //iPad bug requires that we remove the item before setting it
75491         obj.removeItem(key);
75492         obj.setItem(key, Ext.encode(data));
75493     },
75494
75495     /**
75496      * @private
75497      * Physically removes a given record from the local storage. Used internally by {@link #destroy}, which you should
75498      * use instead because it updates the list of currently-stored record ids
75499      * @param {String|Number|Ext.data.Model} id The id of the record to remove, or an Ext.data.Model instance
75500      */
75501     removeRecord: function(id, updateIds) {
75502         var me = this,
75503             ids;
75504             
75505         if (id.isModel) {
75506             id = id.getId();
75507         }
75508
75509         if (updateIds !== false) {
75510             ids = me.getIds();
75511             Ext.Array.remove(ids, id);
75512             me.setIds(ids);
75513         }
75514
75515         me.getStorageObject().removeItem(me.getRecordKey(id));
75516     },
75517
75518     /**
75519      * @private
75520      * Given the id of a record, returns a unique string based on that id and the id of this proxy. This is used when
75521      * storing data in the local storage object and should prevent naming collisions.
75522      * @param {String|Number|Ext.data.Model} id The record id, or a Model instance
75523      * @return {String} The unique key for this record
75524      */
75525     getRecordKey: function(id) {
75526         if (id.isModel) {
75527             id = id.getId();
75528         }
75529
75530         return Ext.String.format("{0}-{1}", this.id, id);
75531     },
75532
75533     /**
75534      * @private
75535      * Returns the unique key used to store the current record counter for this proxy. This is used internally when
75536      * realizing models (creating them when they used to be phantoms), in order to give each model instance a unique id.
75537      * @return {String} The counter key
75538      */
75539     getRecordCounterKey: function() {
75540         return Ext.String.format("{0}-counter", this.id);
75541     },
75542
75543     /**
75544      * @private
75545      * Returns the array of record IDs stored in this Proxy
75546      * @return {Array} The record IDs. Each is cast as a Number
75547      */
75548     getIds: function() {
75549         var ids    = (this.getStorageObject().getItem(this.id) || "").split(","),
75550             length = ids.length,
75551             i;
75552
75553         if (length == 1 && ids[0] === "") {
75554             ids = [];
75555         } else {
75556             for (i = 0; i < length; i++) {
75557                 ids[i] = parseInt(ids[i], 10);
75558             }
75559         }
75560
75561         return ids;
75562     },
75563
75564     /**
75565      * @private
75566      * Saves the array of ids representing the set of all records in the Proxy
75567      * @param {Array} ids The ids to set
75568      */
75569     setIds: function(ids) {
75570         var obj = this.getStorageObject(),
75571             str = ids.join(",");
75572         
75573         obj.removeItem(this.id);
75574         
75575         if (!Ext.isEmpty(str)) {
75576             obj.setItem(this.id, str);
75577         }
75578     },
75579
75580     /**
75581      * @private
75582      * Returns the next numerical ID that can be used when realizing a model instance (see getRecordCounterKey). Increments
75583      * the counter.
75584      * @return {Number} The id
75585      */
75586     getNextId: function() {
75587         var obj  = this.getStorageObject(),
75588             key  = this.getRecordCounterKey(),
75589             last = obj.getItem(key),
75590             ids, id;
75591         
75592         if (last === null) {
75593             ids = this.getIds();
75594             last = ids[ids.length - 1] || 0;
75595         }
75596         
75597         id = parseInt(last, 10) + 1;
75598         obj.setItem(key, id);
75599         
75600         return id;
75601     },
75602
75603     /**
75604      * @private
75605      * Sets up the Proxy by claiming the key in the storage object that corresponds to the unique id of this Proxy. Called
75606      * automatically by the constructor, this should not need to be called again unless {@link #clear} has been called.
75607      */
75608     initialize: function() {
75609         var storageObject = this.getStorageObject();
75610         storageObject.setItem(this.id, storageObject.getItem(this.id) || "");
75611     },
75612
75613     /**
75614      * Destroys all records stored in the proxy and removes all keys and values used to support the proxy from the storage object
75615      */
75616     clear: function() {
75617         var obj = this.getStorageObject(),
75618             ids = this.getIds(),
75619             len = ids.length,
75620             i;
75621
75622         //remove all the records
75623         for (i = 0; i < len; i++) {
75624             this.removeRecord(ids[i]);
75625         }
75626
75627         //remove the supporting objects
75628         obj.removeItem(this.getRecordCounterKey());
75629         obj.removeItem(this.id);
75630     },
75631
75632     /**
75633      * @private
75634      * Abstract function which should return the storage object that data will be saved to. This must be implemented
75635      * in each subclass.
75636      * @return {Object} The storage object
75637      */
75638     getStorageObject: function() {
75639         Ext.Error.raise("The getStorageObject function has not been defined in your Ext.data.proxy.WebStorage subclass");
75640     }
75641 });
75642 /**
75643  * @author Ed Spencer
75644  * @class Ext.data.proxy.LocalStorage
75645  * @extends Ext.data.proxy.WebStorage
75646  * 
75647  * <p>The LocalStorageProxy uses the new HTML5 localStorage API to save {@link Ext.data.Model Model} data locally on
75648  * the client browser. HTML5 localStorage is a key-value store (e.g. cannot save complex objects like JSON), so
75649  * LocalStorageProxy automatically serializes and deserializes data when saving and retrieving it.</p>
75650  * 
75651  * <p>localStorage is extremely useful for saving user-specific information without needing to build server-side 
75652  * infrastructure to support it. Let's imagine we're writing a Twitter search application and want to save the user's
75653  * searches locally so they can easily perform a saved search again later. We'd start by creating a Search model:</p>
75654  * 
75655 <pre><code>
75656 Ext.define('Search', {
75657     fields: ['id', 'query'],
75658     extend: 'Ext.data.Model',
75659     proxy: {
75660         type: 'localstorage',
75661         id  : 'twitter-Searches'
75662     }
75663 });
75664 </code></pre>
75665  * 
75666  * <p>Our Search model contains just two fields - id and query - plus a Proxy definition. The only configuration we
75667  * need to pass to the LocalStorage proxy is an {@link #id}. This is important as it separates the Model data in this
75668  * Proxy from all others. The localStorage API puts all data into a single shared namespace, so by setting an id we
75669  * enable LocalStorageProxy to manage the saved Search data.</p>
75670  * 
75671  * <p>Saving our data into localStorage is easy and would usually be done with a {@link Ext.data.Store Store}:</p>
75672  * 
75673 <pre><code>
75674 //our Store automatically picks up the LocalStorageProxy defined on the Search model
75675 var store = new Ext.data.Store({
75676     model: "Search"
75677 });
75678
75679 //loads any existing Search data from localStorage
75680 store.load();
75681
75682 //now add some Searches
75683 store.add({query: 'Sencha Touch'});
75684 store.add({query: 'Ext JS'});
75685
75686 //finally, save our Search data to localStorage
75687 store.sync();
75688 </code></pre>
75689  * 
75690  * <p>The LocalStorageProxy automatically gives our new Searches an id when we call store.sync(). It encodes the Model
75691  * data and places it into localStorage. We can also save directly to localStorage, bypassing the Store altogether:</p>
75692  * 
75693 <pre><code>
75694 var search = Ext.ModelManager.create({query: 'Sencha Animator'}, 'Search');
75695
75696 //uses the configured LocalStorageProxy to save the new Search to localStorage
75697 search.save();
75698 </code></pre>
75699  * 
75700  * <p><u>Limitations</u></p>
75701  * 
75702  * <p>If this proxy is used in a browser where local storage is not supported, the constructor will throw an error.
75703  * A local storage proxy requires a unique ID which is used as a key in which all record data are stored in the
75704  * local storage object.</p>
75705  * 
75706  * <p>It's important to supply this unique ID as it cannot be reliably determined otherwise. If no id is provided
75707  * but the attached store has a storeId, the storeId will be used. If neither option is presented the proxy will
75708  * throw an error.</p>
75709  */
75710 Ext.define('Ext.data.proxy.LocalStorage', {
75711     extend: 'Ext.data.proxy.WebStorage',
75712     alias: 'proxy.localstorage',
75713     alternateClassName: 'Ext.data.LocalStorageProxy',
75714     
75715     //inherit docs
75716     getStorageObject: function() {
75717         return window.localStorage;
75718     }
75719 });
75720 /**
75721  * @author Ed Spencer
75722  * @class Ext.data.proxy.Memory
75723  * @extends Ext.data.proxy.Client
75724  *
75725  * <p>In-memory proxy. This proxy simply uses a local variable for data storage/retrieval, so its contents are lost on
75726  * every page refresh.</p>
75727  *
75728  * <p>Usually this Proxy isn't used directly, serving instead as a helper to a {@link Ext.data.Store Store} where a
75729  * reader is required to load data. For example, say we have a Store for a User model and have some inline data we want
75730  * to load, but this data isn't in quite the right format: we can use a MemoryProxy with a JsonReader to read it into
75731  * our Store:</p>
75732  *
75733 <pre><code>
75734 //this is the model we will be using in the store
75735 Ext.define('User', {
75736     extend: 'Ext.data.Model',
75737     fields: [
75738         {name: 'id',    type: 'int'},
75739         {name: 'name',  type: 'string'},
75740         {name: 'phone', type: 'string', mapping: 'phoneNumber'}
75741     ]
75742 });
75743
75744 //this data does not line up to our model fields - the phone field is called phoneNumber
75745 var data = {
75746     users: [
75747         {
75748             id: 1,
75749             name: 'Ed Spencer',
75750             phoneNumber: '555 1234'
75751         },
75752         {
75753             id: 2,
75754             name: 'Abe Elias',
75755             phoneNumber: '666 1234'
75756         }
75757     ]
75758 };
75759
75760 //note how we set the 'root' in the reader to match the data structure above
75761 var store = new Ext.data.Store({
75762     autoLoad: true,
75763     model: 'User',
75764     data : data,
75765     proxy: {
75766         type: 'memory',
75767         reader: {
75768             type: 'json',
75769             root: 'users'
75770         }
75771     }
75772 });
75773 </code></pre>
75774  */
75775 Ext.define('Ext.data.proxy.Memory', {
75776     extend: 'Ext.data.proxy.Client',
75777     alias: 'proxy.memory',
75778     alternateClassName: 'Ext.data.MemoryProxy',
75779
75780     /**
75781      * @cfg {Array} data Optional array of Records to load into the Proxy
75782      */
75783
75784     constructor: function(config) {
75785         this.callParent([config]);
75786
75787         //ensures that the reader has been instantiated properly
75788         this.setReader(this.reader);
75789     },
75790
75791     /**
75792      * Reads data from the configured {@link #data} object. Uses the Proxy's {@link #reader}, if present
75793      * @param {Ext.data.Operation} operation The read Operation
75794      * @param {Function} callback The callback to call when reading has completed
75795      * @param {Object} scope The scope to call the callback function in
75796      */
75797     read: function(operation, callback, scope) {
75798         var me     = this,
75799             reader = me.getReader(),
75800             result = reader.read(me.data);
75801
75802         Ext.apply(operation, {
75803             resultSet: result
75804         });
75805
75806         operation.setCompleted();
75807         operation.setSuccessful();
75808         Ext.callback(callback, scope || me, [operation]);
75809     },
75810
75811     clear: Ext.emptyFn
75812 });
75813
75814 /**
75815  * @author Ed Spencer
75816  * @class Ext.data.proxy.Rest
75817  * @extends Ext.data.proxy.Ajax
75818  * 
75819  * <p>RestProxy is a specialization of the {@link Ext.data.proxy.Ajax AjaxProxy} which simply maps the four actions 
75820  * (create, read, update and destroy) to RESTful HTTP verbs. For example, let's set up a {@link Ext.data.Model Model}
75821  * with an inline RestProxy</p>
75822  * 
75823 <pre><code>
75824 Ext.define('User', {
75825     extend: 'Ext.data.Model',
75826     fields: ['id', 'name', 'email'],
75827
75828     proxy: {
75829         type: 'rest',
75830         url : '/users'
75831     }
75832 });
75833 </code></pre>
75834  * 
75835  * <p>Now we can create a new User instance and save it via the RestProxy. Doing this will cause the Proxy to send a
75836  * POST request to '/users':
75837  * 
75838 <pre><code>
75839 var user = Ext.ModelManager.create({name: 'Ed Spencer', email: 'ed@sencha.com'}, 'User');
75840
75841 user.save(); //POST /users
75842 </code></pre>
75843  * 
75844  * <p>Let's expand this a little and provide a callback for the {@link Ext.data.Model#save} call to update the Model
75845  * once it has been created. We'll assume the creation went successfully and that the server gave this user an ID of 
75846  * 123:</p>
75847  * 
75848 <pre><code>
75849 user.save({
75850     success: function(user) {
75851         user.set('name', 'Khan Noonien Singh');
75852
75853         user.save(); //PUT /users/123
75854     }
75855 });
75856 </code></pre>
75857  * 
75858  * <p>Now that we're no longer creating a new Model instance, the request method is changed to an HTTP PUT, targeting
75859  * the relevant url for that user. Now let's delete this user, which will use the DELETE method:</p>
75860  * 
75861 <pre><code>
75862     user.destroy(); //DELETE /users/123
75863 </code></pre>
75864  * 
75865  * <p>Finally, when we perform a load of a Model or Store, RestProxy will use the GET method:</p>
75866  * 
75867 <pre><code>
75868 //1. Load via Store
75869
75870 //the Store automatically picks up the Proxy from the User model
75871 var store = new Ext.data.Store({
75872     model: 'User'
75873 });
75874
75875 store.load(); //GET /users
75876
75877 //2. Load directly from the Model
75878
75879 //GET /users/123
75880 Ext.ModelManager.getModel('User').load(123, {
75881     success: function(user) {
75882         console.log(user.getId()); //outputs 123
75883     }
75884 });
75885 </code></pre>
75886  * 
75887  * <p><u>Url generation</u></p>
75888  * 
75889  * <p>RestProxy is able to automatically generate the urls above based on two configuration options - {@link #appendId}
75890  * and {@link #format}. If appendId is true (it is by default) then RestProxy will automatically append the ID of the 
75891  * Model instance in question to the configured url, resulting in the '/users/123' that we saw above.</p>
75892  * 
75893  * <p>If the request is not for a specific Model instance (e.g. loading a Store), the url is not appended with an id. 
75894  * RestProxy will automatically insert a '/' before the ID if one is not already present.</p>
75895  * 
75896 <pre><code>
75897 new Ext.data.proxy.Rest({
75898     url: '/users',
75899     appendId: true //default
75900 });
75901
75902 // Collection url: /users
75903 // Instance url  : /users/123
75904 </code></pre>
75905  * 
75906  * <p>RestProxy can also optionally append a format string to the end of any generated url:</p>
75907  * 
75908 <pre><code>
75909 new Ext.data.proxy.Rest({
75910     url: '/users',
75911     format: 'json'
75912 });
75913
75914 // Collection url: /users.json
75915 // Instance url  : /users/123.json
75916 </code></pre>
75917  * 
75918  * <p>If further customization is needed, simply implement the {@link #buildUrl} method and add your custom generated
75919  * url onto the {@link Ext.data.Request Request} object that is passed to buildUrl. See 
75920  * <a href="source/RestProxy.html#method-Ext.data.proxy.Rest-buildUrl">RestProxy's implementation</a> for an example of
75921  * how to achieve this.</p>
75922  * 
75923  * <p>Note that RestProxy inherits from {@link Ext.data.proxy.Ajax AjaxProxy}, which already injects all of the sorter,
75924  * filter, group and paging options into the generated url. See the {@link Ext.data.proxy.Ajax AjaxProxy docs} for more
75925  * details.</p>
75926  */
75927 Ext.define('Ext.data.proxy.Rest', {
75928     extend: 'Ext.data.proxy.Ajax',
75929     alternateClassName: 'Ext.data.RestProxy',
75930     alias : 'proxy.rest',
75931     
75932     /**
75933      * @cfg {Boolean} appendId True to automatically append the ID of a Model instance when performing a request based
75934      * on that single instance. See RestProxy intro docs for more details. Defaults to true.
75935      */
75936     appendId: true,
75937     
75938     /**
75939      * @cfg {String} format Optional data format to send to the server when making any request (e.g. 'json'). See the
75940      * RestProxy intro docs for full details. Defaults to undefined.
75941      */
75942     
75943     /**
75944      * @cfg {Boolean} batchActions True to batch actions of a particular type when synchronizing the store.
75945      * Defaults to <tt>false</tt>.
75946      */
75947     batchActions: false,
75948     
75949     /**
75950      * Specialized version of buildUrl that incorporates the {@link #appendId} and {@link #format} options into the
75951      * generated url. Override this to provide further customizations, but remember to call the superclass buildUrl
75952      * so that additional parameters like the cache buster string are appended
75953      */
75954     buildUrl: function(request) {
75955         var me        = this,
75956             operation = request.operation,
75957             records   = operation.records || [],
75958             record    = records[0],
75959             format    = me.format,
75960             url       = me.getUrl(request),
75961             id        = record ? record.getId() : operation.id;
75962         
75963         if (me.appendId && id) {
75964             if (!url.match(/\/$/)) {
75965                 url += '/';
75966             }
75967             
75968             url += id;
75969         }
75970         
75971         if (format) {
75972             if (!url.match(/\.$/)) {
75973                 url += '.';
75974             }
75975             
75976             url += format;
75977         }
75978         
75979         request.url = url;
75980         
75981         return me.callParent(arguments);
75982     }
75983 }, function() {
75984     Ext.apply(this.prototype, {
75985         /**
75986          * Mapping of action name to HTTP request method. These default to RESTful conventions for the 'create', 'read',
75987          * 'update' and 'destroy' actions (which map to 'POST', 'GET', 'PUT' and 'DELETE' respectively). This object should
75988          * not be changed except globally via {@link Ext#override Ext.override} - the {@link #getMethod} function can be overridden instead.
75989          * @property actionMethods
75990          * @type Object
75991          */
75992         actionMethods: {
75993             create : 'POST',
75994             read   : 'GET',
75995             update : 'PUT',
75996             destroy: 'DELETE'
75997         }
75998     });
75999 });
76000
76001 /**
76002  * @author Ed Spencer
76003  * @class Ext.data.proxy.SessionStorage
76004  * @extends Ext.data.proxy.WebStorage
76005  * 
76006  * <p>Proxy which uses HTML5 session storage as its data storage/retrieval mechanism.
76007  * If this proxy is used in a browser where session storage is not supported, the constructor will throw an error.
76008  * A session storage proxy requires a unique ID which is used as a key in which all record data are stored in the
76009  * session storage object.</p>
76010  * 
76011  * <p>It's important to supply this unique ID as it cannot be reliably determined otherwise. If no id is provided
76012  * but the attached store has a storeId, the storeId will be used. If neither option is presented the proxy will
76013  * throw an error.</p>
76014  * 
76015  * <p>Proxies are almost always used with a {@link Ext.data.Store store}:<p>
76016  * 
76017 <pre><code>
76018 new Ext.data.Store({
76019     proxy: {
76020         type: 'sessionstorage',
76021         id  : 'myProxyKey'
76022     }
76023 });
76024 </code></pre>
76025  * 
76026  * <p>Alternatively you can instantiate the Proxy directly:</p>
76027  * 
76028 <pre><code>
76029 new Ext.data.proxy.SessionStorage({
76030     id  : 'myOtherProxyKey'
76031 });
76032  </code></pre>
76033  * 
76034  * <p>Note that session storage is different to local storage (see {@link Ext.data.proxy.LocalStorage}) - if a browser
76035  * session is ended (e.g. by closing the browser) then all data in a SessionStorageProxy are lost. Browser restarts
76036  * don't affect the {@link Ext.data.proxy.LocalStorage} - the data are preserved.</p>
76037  */
76038 Ext.define('Ext.data.proxy.SessionStorage', {
76039     extend: 'Ext.data.proxy.WebStorage',
76040     alias: 'proxy.sessionstorage',
76041     alternateClassName: 'Ext.data.SessionStorageProxy',
76042     
76043     //inherit docs
76044     getStorageObject: function() {
76045         return window.sessionStorage;
76046     }
76047 });
76048
76049 /**
76050  * @author Ed Spencer
76051  * @class Ext.data.reader.Array
76052  * @extends Ext.data.reader.Json
76053  * 
76054  * <p>Data reader class to create an Array of {@link Ext.data.Model} objects from an Array.
76055  * Each element of that Array represents a row of data fields. The
76056  * fields are pulled into a Record object using as a subscript, the <code>mapping</code> property
76057  * of the field definition if it exists, or the field's ordinal position in the definition.</p>
76058  * 
76059  * <p><u>Example code:</u></p>
76060  * 
76061 <pre><code>
76062 Employee = Ext.define('Employee', {
76063     extend: 'Ext.data.Model',
76064     fields: [
76065         'id',
76066         {name: 'name', mapping: 1},         // "mapping" only needed if an "id" field is present which
76067         {name: 'occupation', mapping: 2}    // precludes using the ordinal position as the index.        
76068     ]
76069 });
76070
76071 var myReader = new Ext.data.reader.Array({
76072     model: 'Employee'
76073 }, Employee);
76074 </code></pre>
76075  * 
76076  * <p>This would consume an Array like this:</p>
76077  * 
76078 <pre><code>
76079 [ [1, 'Bill', 'Gardener'], [2, 'Ben', 'Horticulturalist'] ]
76080 </code></pre>
76081  * 
76082  * @constructor
76083  * Create a new ArrayReader
76084  * @param {Object} meta Metadata configuration options.
76085  */
76086 Ext.define('Ext.data.reader.Array', {
76087     extend: 'Ext.data.reader.Json',
76088     alternateClassName: 'Ext.data.ArrayReader',
76089     alias : 'reader.array',
76090
76091     /**
76092      * @private
76093      * Most of the work is done for us by JsonReader, but we need to overwrite the field accessors to just
76094      * reference the correct position in the array.
76095      */
76096     buildExtractors: function() {
76097         this.callParent(arguments);
76098         
76099         var fields = this.model.prototype.fields.items,
76100             length = fields.length,
76101             extractorFunctions = [],
76102             i;
76103         
76104         for (i = 0; i < length; i++) {
76105             extractorFunctions.push(function(index) {
76106                 return function(data) {
76107                     return data[index];
76108                 };
76109             }(fields[i].mapping || i));
76110         }
76111         
76112         this.extractorFunctions = extractorFunctions;
76113     }
76114 });
76115
76116 /**
76117  * @author Ed Spencer
76118  * @class Ext.data.reader.Xml
76119  * @extends Ext.data.reader.Reader
76120  * 
76121  * <p>The XML Reader is used by a Proxy to read a server response that is sent back in XML format. This usually
76122  * happens as a result of loading a Store - for example we might create something like this:</p>
76123  * 
76124 <pre><code>
76125 Ext.define('User', {
76126     extend: 'Ext.data.Model',
76127     fields: ['id', 'name', 'email']
76128 });
76129
76130 var store = new Ext.data.Store({
76131     model: 'User',
76132     proxy: {
76133         type: 'ajax',
76134         url : 'users.xml',
76135         reader: {
76136             type: 'xml',
76137             record: 'user'
76138         }
76139     }
76140 });
76141 </code></pre>
76142  * 
76143  * <p>The example above creates a 'User' model. Models are explained in the {@link Ext.data.Model Model} docs if you're
76144  * not already familiar with them.</p>
76145  * 
76146  * <p>We created the simplest type of XML Reader possible by simply telling our {@link Ext.data.Store Store}'s 
76147  * {@link Ext.data.proxy.Proxy Proxy} that we want a XML Reader. The Store automatically passes the configured model to the
76148  * Store, so it is as if we passed this instead:
76149  * 
76150 <pre><code>
76151 reader: {
76152     type : 'xml',
76153     model: 'User',
76154     record: 'user'
76155 }
76156 </code></pre>
76157  * 
76158  * <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>
76159  *
76160 <pre><code>
76161 &lt;?xml version="1.0" encoding="UTF-8"?&gt;
76162 &lt;user&gt;
76163     &lt;id&gt;1&lt;/id&gt;
76164     &lt;name&gt;Ed Spencer&lt;/name&gt;
76165     &lt;email&gt;ed@sencha.com&lt;/email&gt;
76166 &lt;/user&gt;
76167 &lt;user&gt;
76168     &lt;id&gt;2&lt;/id&gt;
76169     &lt;name&gt;Abe Elias&lt;/name&gt;
76170     &lt;email&gt;abe@sencha.com&lt;/email&gt;
76171 &lt;/user&gt;
76172 </code></pre>
76173  * 
76174  * <p>The XML Reader uses the configured {@link #record} option to pull out the data for each record - in this case we
76175  * set record to 'user', so each &lt;user&gt; above will be converted into a User model.</p>
76176  * 
76177  * <p><u>Reading other XML formats</u></p>
76178  * 
76179  * <p>If you already have your XML format defined and it doesn't look quite like what we have above, you can usually
76180  * pass XmlReader a couple of configuration options to make it parse your format. For example, we can use the 
76181  * {@link #root} configuration to parse data that comes back like this:</p>
76182  * 
76183 <pre><code>
76184 &lt;?xml version="1.0" encoding="UTF-8"?&gt;
76185 &lt;users&gt;
76186     &lt;user&gt;
76187         &lt;id&gt;1&lt;/id&gt;
76188         &lt;name&gt;Ed Spencer&lt;/name&gt;
76189         &lt;email&gt;ed@sencha.com&lt;/email&gt;
76190     &lt;/user&gt;
76191     &lt;user&gt;
76192         &lt;id&gt;2&lt;/id&gt;
76193         &lt;name&gt;Abe Elias&lt;/name&gt;
76194         &lt;email&gt;abe@sencha.com&lt;/email&gt;
76195     &lt;/user&gt;
76196 &lt;/users&gt;
76197 </code></pre>
76198  * 
76199  * <p>To parse this we just pass in a {@link #root} configuration that matches the 'users' above:</p>
76200  * 
76201 <pre><code>
76202 reader: {
76203     type  : 'xml',
76204     root  : 'users',
76205     record: 'user'
76206 }
76207 </code></pre>
76208  * 
76209  * <p>Note that XmlReader doesn't care whether your {@link #root} and {@link #record} elements are nested deep inside
76210  * a larger structure, so a response like this will still work:
76211  * 
76212 <pre><code>
76213 &lt;?xml version="1.0" encoding="UTF-8"?&gt;
76214 &lt;deeply&gt;
76215     &lt;nested&gt;
76216         &lt;xml&gt;
76217             &lt;users&gt;
76218                 &lt;user&gt;
76219                     &lt;id&gt;1&lt;/id&gt;
76220                     &lt;name&gt;Ed Spencer&lt;/name&gt;
76221                     &lt;email&gt;ed@sencha.com&lt;/email&gt;
76222                 &lt;/user&gt;
76223                 &lt;user&gt;
76224                     &lt;id&gt;2&lt;/id&gt;
76225                     &lt;name&gt;Abe Elias&lt;/name&gt;
76226                     &lt;email&gt;abe@sencha.com&lt;/email&gt;
76227                 &lt;/user&gt;
76228             &lt;/users&gt;
76229         &lt;/xml&gt;
76230     &lt;/nested&gt;
76231 &lt;/deeply&gt;
76232 </code></pre>
76233  * 
76234  * <p><u>Response metadata</u></p>
76235  * 
76236  * <p>The server can return additional data in its response, such as the {@link #totalProperty total number of records} 
76237  * and the {@link #successProperty success status of the response}. These are typically included in the XML response
76238  * like this:</p>
76239  * 
76240 <pre><code>
76241 &lt;?xml version="1.0" encoding="UTF-8"?&gt;
76242 &lt;total&gt;100&lt;/total&gt;
76243 &lt;success&gt;true&lt;/success&gt;
76244 &lt;users&gt;
76245     &lt;user&gt;
76246         &lt;id&gt;1&lt;/id&gt;
76247         &lt;name&gt;Ed Spencer&lt;/name&gt;
76248         &lt;email&gt;ed@sencha.com&lt;/email&gt;
76249     &lt;/user&gt;
76250     &lt;user&gt;
76251         &lt;id&gt;2&lt;/id&gt;
76252         &lt;name&gt;Abe Elias&lt;/name&gt;
76253         &lt;email&gt;abe@sencha.com&lt;/email&gt;
76254     &lt;/user&gt;
76255 &lt;/users&gt;
76256 </code></pre>
76257  * 
76258  * <p>If these properties are present in the XML response they can be parsed out by the XmlReader and used by the
76259  * Store that loaded it. We can set up the names of these properties by specifying a final pair of configuration 
76260  * options:</p>
76261  * 
76262 <pre><code>
76263 reader: {
76264     type: 'xml',
76265     root: 'users',
76266     totalProperty  : 'total',
76267     successProperty: 'success'
76268 }
76269 </code></pre>
76270  * 
76271  * <p>These final options are not necessary to make the Reader work, but can be useful when the server needs to report
76272  * an error or if it needs to indicate that there is a lot of data available of which only a subset is currently being
76273  * returned.</p>
76274  * 
76275  * <p><u>Response format</u></p>
76276  * 
76277  * <p><b>Note:</b> in order for the browser to parse a returned XML document, the Content-Type header in the HTTP 
76278  * response must be set to "text/xml" or "application/xml". This is very important - the XmlReader will not
76279  * work correctly otherwise.</p>
76280  */
76281 Ext.define('Ext.data.reader.Xml', {
76282     extend: 'Ext.data.reader.Reader',
76283     alternateClassName: 'Ext.data.XmlReader',
76284     alias : 'reader.xml',
76285     
76286     /**
76287      * @private
76288      * Creates a function to return some particular key of data from a response. The totalProperty and
76289      * successProperty are treated as special cases for type casting, everything else is just a simple selector.
76290      * @param {String} key
76291      * @return {Function}
76292      */
76293
76294     /**
76295      * @cfg {String} record The DomQuery path to the repeated element which contains record information.
76296      */
76297
76298     createAccessor: function() {
76299         var selectValue = function(expr, root){
76300             var node = Ext.DomQuery.selectNode(expr, root),
76301                 val;
76302                 
76303             
76304             
76305         };
76306
76307         return function(expr) {
76308             var me = this;
76309             
76310             if (Ext.isEmpty(expr)) {
76311                 return Ext.emptyFn;
76312             }
76313             
76314             if (Ext.isFunction(expr)) {
76315                 return expr;
76316             }
76317             
76318             return function(root) {
76319                 var node = Ext.DomQuery.selectNode(expr, root),
76320                     val = me.getNodeValue(node);
76321                     
76322                 return Ext.isEmpty(val) ? null : val;
76323             };
76324         };
76325     }(),
76326     
76327     getNodeValue: function(node) {
76328         var val;
76329         if (node && node.firstChild) {
76330             val = node.firstChild.nodeValue;
76331         }
76332         return val || null;
76333     },
76334
76335     //inherit docs
76336     getResponseData: function(response) {
76337         var xml = response.responseXML;
76338
76339         if (!xml) {
76340             Ext.Error.raise({
76341                 response: response,
76342                 msg: 'XML data not found in the response'
76343             });
76344         }
76345
76346         return xml;
76347     },
76348
76349     /**
76350      * Normalizes the data object
76351      * @param {Object} data The raw data object
76352      * @return {Object} Returns the documentElement property of the data object if present, or the same object if not
76353      */
76354     getData: function(data) {
76355         return data.documentElement || data;
76356     },
76357
76358     /**
76359      * @private
76360      * Given an XML object, returns the Element that represents the root as configured by the Reader's meta data
76361      * @param {Object} data The XML data object
76362      * @return {Element} The root node element
76363      */
76364     getRoot: function(data) {
76365         var nodeName = data.nodeName,
76366             root     = this.root;
76367         
76368         if (!root || (nodeName && nodeName == root)) {
76369             return data;
76370         } else if (Ext.DomQuery.isXml(data)) {
76371             // This fix ensures we have XML data
76372             // Related to TreeStore calling getRoot with the root node, which isn't XML
76373             // Probably should be resolved in TreeStore at some point
76374             return Ext.DomQuery.selectNode(root, data);
76375         }
76376     },
76377
76378     /**
76379      * @private
76380      * We're just preparing the data for the superclass by pulling out the record nodes we want
76381      * @param {Element} root The XML root node
76382      * @return {Array} The records
76383      */
76384     extractData: function(root) {
76385         var recordName = this.record;
76386         
76387         if (!recordName) {
76388             Ext.Error.raise('Record is a required parameter');
76389         }
76390         
76391         if (recordName != root.nodeName) {
76392             root = Ext.DomQuery.select(recordName, root);
76393         } else {
76394             root = [root];
76395         }
76396         return this.callParent([root]);
76397     },
76398     
76399     /**
76400      * @private
76401      * See Ext.data.reader.Reader's getAssociatedDataRoot docs
76402      * @param {Mixed} data The raw data object
76403      * @param {String} associationName The name of the association to get data for (uses associationKey if present)
76404      * @return {Mixed} The root
76405      */
76406     getAssociatedDataRoot: function(data, associationName) {
76407         return Ext.DomQuery.select(associationName, data)[0];
76408     },
76409
76410     /**
76411      * Parses an XML document and returns a ResultSet containing the model instances
76412      * @param {Object} doc Parsed XML document
76413      * @return {Ext.data.ResultSet} The parsed result set
76414      */
76415     readRecords: function(doc) {
76416         //it's possible that we get passed an array here by associations. Make sure we strip that out (see Ext.data.reader.Reader#readAssociated)
76417         if (Ext.isArray(doc)) {
76418             doc = doc[0];
76419         }
76420         
76421         /**
76422          * DEPRECATED - will be removed in Ext JS 5.0. This is just a copy of this.rawData - use that instead
76423          * @property xmlData
76424          * @type Object
76425          */
76426         this.xmlData = doc;
76427         return this.callParent([doc]);
76428     }
76429 });
76430
76431 /**
76432  * @author Ed Spencer
76433  * @class Ext.data.writer.Xml
76434  * @extends Ext.data.writer.Writer
76435  * 
76436  * <p>Writer that outputs model data in XML format</p>
76437  */
76438 Ext.define('Ext.data.writer.Xml', {
76439     
76440     /* Begin Definitions */
76441     
76442     extend: 'Ext.data.writer.Writer',
76443     alternateClassName: 'Ext.data.XmlWriter',
76444     
76445     alias: 'writer.xml',
76446     
76447     /* End Definitions */
76448     
76449     /**
76450      * @cfg {String} documentRoot The name of the root element of the document. Defaults to <tt>'xmlData'</tt>.
76451      * If there is more than 1 record and the root is not specified, the default document root will still be used
76452      * to ensure a valid XML document is created.
76453      */
76454     documentRoot: 'xmlData',
76455     
76456     /**
76457      * @cfg {String} defaultDocumentRoot The root to be used if {@link #documentRoot} is empty and a root is required
76458      * to form a valid XML document.
76459      */
76460     defaultDocumentRoot: 'xmlData',
76461
76462     /**
76463      * @cfg {String} header A header to use in the XML document (such as setting the encoding or version).
76464      * Defaults to <tt>''</tt>.
76465      */
76466     header: '',
76467
76468     /**
76469      * @cfg {String} record The name of the node to use for each record. Defaults to <tt>'record'</tt>.
76470      */
76471     record: 'record',
76472
76473     //inherit docs
76474     writeRecords: function(request, data) {
76475         var me = this,
76476             xml = [],
76477             i = 0,
76478             len = data.length,
76479             root = me.documentRoot,
76480             record = me.record,
76481             needsRoot = data.length !== 1,
76482             item,
76483             key;
76484             
76485         // may not exist
76486         xml.push(me.header || '');
76487         
76488         if (!root && needsRoot) {
76489             root = me.defaultDocumentRoot;
76490         }
76491         
76492         if (root) {
76493             xml.push('<', root, '>');
76494         }
76495             
76496         for (; i < len; ++i) {
76497             item = data[i];
76498             xml.push('<', record, '>');
76499             for (key in item) {
76500                 if (item.hasOwnProperty(key)) {
76501                     xml.push('<', key, '>', item[key], '</', key, '>');
76502                 }
76503             }
76504             xml.push('</', record, '>');
76505         }
76506         
76507         if (root) {
76508             xml.push('</', root, '>');
76509         }
76510             
76511         request.xmlData = xml.join('');
76512         return request;
76513     }
76514 });
76515
76516 /**
76517  * @class Ext.direct.Event
76518  * A base class for all Ext.direct events. An event is
76519  * created after some kind of interaction with the server.
76520  * The event class is essentially just a data structure
76521  * to hold a direct response.
76522  * 
76523  * @constructor
76524  * @param {Object} config The config object
76525  */
76526 Ext.define('Ext.direct.Event', {
76527     
76528     /* Begin Definitions */
76529    
76530     alias: 'direct.event',
76531     
76532     requires: ['Ext.direct.Manager'],
76533     
76534     /* End Definitions */
76535    
76536     status: true,
76537     
76538     constructor: function(config) {
76539         Ext.apply(this, config);
76540     },
76541     
76542     /**
76543      * Return the raw data for this event.
76544      * @return {Object} The data from the event
76545      */
76546     getData: function(){
76547         return this.data;
76548     }
76549 });
76550
76551 /**
76552  * @class Ext.direct.RemotingEvent
76553  * @extends Ext.direct.Event
76554  * An event that is fired when data is received from a 
76555  * {@link Ext.direct.RemotingProvider}. Contains a method to the
76556  * related transaction for the direct request, see {@link #getTransaction}
76557  */
76558 Ext.define('Ext.direct.RemotingEvent', {
76559     
76560     /* Begin Definitions */
76561    
76562     extend: 'Ext.direct.Event',
76563     
76564     alias: 'direct.rpc',
76565     
76566     /* End Definitions */
76567     
76568     /**
76569      * Get the transaction associated with this event.
76570      * @return {Ext.direct.Transaction} The transaction
76571      */
76572     getTransaction: function(){
76573         return this.transaction || Ext.direct.Manager.getTransaction(this.tid);
76574     }
76575 });
76576
76577 /**
76578  * @class Ext.direct.ExceptionEvent
76579  * @extends Ext.direct.RemotingEvent
76580  * An event that is fired when an exception is received from a {@link Ext.direct.RemotingProvider}
76581  */
76582 Ext.define('Ext.direct.ExceptionEvent', {
76583     
76584     /* Begin Definitions */
76585    
76586     extend: 'Ext.direct.RemotingEvent',
76587     
76588     alias: 'direct.exception',
76589     
76590     /* End Definitions */
76591    
76592    status: false
76593 });
76594
76595 /**
76596  * @class Ext.direct.Provider
76597  * <p>Ext.direct.Provider is an abstract class meant to be extended.</p>
76598  * 
76599  * <p>For example ExtJs implements the following subclasses:</p>
76600  * <pre><code>
76601 Provider
76602 |
76603 +---{@link Ext.direct.JsonProvider JsonProvider} 
76604     |
76605     +---{@link Ext.direct.PollingProvider PollingProvider}   
76606     |
76607     +---{@link Ext.direct.RemotingProvider RemotingProvider}   
76608  * </code></pre>
76609  * @abstract
76610  */
76611 Ext.define('Ext.direct.Provider', {
76612     
76613     /* Begin Definitions */
76614    
76615    alias: 'direct.provider',
76616    
76617     mixins: {
76618         observable: 'Ext.util.Observable'   
76619     },
76620    
76621     /* End Definitions */
76622    
76623    /**
76624      * @cfg {String} id
76625      * The unique id of the provider (defaults to an {@link Ext#id auto-assigned id}).
76626      * You should assign an id if you need to be able to access the provider later and you do
76627      * not have an object reference available, for example:
76628      * <pre><code>
76629 Ext.direct.Manager.addProvider({
76630     type: 'polling',
76631     url:  'php/poll.php',
76632     id:   'poll-provider'
76633 });     
76634 var p = {@link Ext.direct.Manager}.{@link Ext.direct.Manager#getProvider getProvider}('poll-provider');
76635 p.disconnect();
76636      * </code></pre>
76637      */
76638     
76639     constructor : function(config){
76640         var me = this;
76641         
76642         Ext.apply(me, config);
76643         me.addEvents(
76644             /**
76645              * @event connect
76646              * Fires when the Provider connects to the server-side
76647              * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider}.
76648              */            
76649             'connect',
76650             /**
76651              * @event disconnect
76652              * Fires when the Provider disconnects from the server-side
76653              * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider}.
76654              */            
76655             'disconnect',
76656             /**
76657              * @event data
76658              * Fires when the Provider receives data from the server-side
76659              * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider}.
76660              * @param {event} e The Ext.Direct.Event type that occurred.
76661              */            
76662             'data',
76663             /**
76664              * @event exception
76665              * Fires when the Provider receives an exception from the server-side
76666              */                        
76667             'exception'
76668         );
76669         me.mixins.observable.constructor.call(me, config);
76670     },
76671     
76672     /**
76673      * Returns whether or not the server-side is currently connected.
76674      * Abstract method for subclasses to implement.
76675      */
76676     isConnected: function(){
76677         return false;
76678     },
76679
76680     /**
76681      * Abstract methods for subclasses to implement.
76682      */
76683     connect: Ext.emptyFn,
76684     
76685     /**
76686      * Abstract methods for subclasses to implement.
76687      */
76688     disconnect: Ext.emptyFn
76689 });
76690
76691 /**
76692  * @class Ext.direct.JsonProvider
76693  * @extends Ext.direct.Provider
76694
76695 A base provider for communicating using JSON. This is an abstract class
76696 and should not be instanced directly.
76697
76698  * @markdown
76699  * @abstract
76700  */
76701
76702 Ext.define('Ext.direct.JsonProvider', {
76703     
76704     /* Begin Definitions */
76705     
76706     extend: 'Ext.direct.Provider',
76707     
76708     alias: 'direct.jsonprovider',
76709     
76710     uses: ['Ext.direct.ExceptionEvent'],
76711     
76712     /* End Definitions */
76713    
76714    /**
76715     * Parse the JSON response
76716     * @private
76717     * @param {Object} response The XHR response object
76718     * @return {Object} The data in the response.
76719     */
76720    parseResponse: function(response){
76721         if (!Ext.isEmpty(response.responseText)) {
76722             if (Ext.isObject(response.responseText)) {
76723                 return response.responseText;
76724             }
76725             return Ext.decode(response.responseText);
76726         }
76727         return null;
76728     },
76729
76730     /**
76731      * Creates a set of events based on the XHR response
76732      * @private
76733      * @param {Object} response The XHR response
76734      * @return {Array} An array of Ext.direct.Event
76735      */
76736     createEvents: function(response){
76737         var data = null,
76738             events = [],
76739             event,
76740             i = 0,
76741             len;
76742             
76743         try{
76744             data = this.parseResponse(response);
76745         } catch(e) {
76746             event = Ext.create('Ext.direct.ExceptionEvent', {
76747                 data: e,
76748                 xhr: response,
76749                 code: Ext.direct.Manager.self.exceptions.PARSE,
76750                 message: 'Error parsing json response: \n\n ' + data
76751             });
76752             return [event];
76753         }
76754         
76755         if (Ext.isArray(data)) {
76756             for (len = data.length; i < len; ++i) {
76757                 events.push(this.createEvent(data[i]));
76758             }
76759         } else {
76760             events.push(this.createEvent(data));
76761         }
76762         return events;
76763     },
76764     
76765     /**
76766      * Create an event from a response object
76767      * @param {Object} response The XHR response object
76768      * @return {Ext.direct.Event} The event
76769      */
76770     createEvent: function(response){
76771         return Ext.create('direct.' + response.type, response);
76772     }
76773 });
76774 /**
76775  * @class Ext.direct.PollingProvider
76776  * @extends Ext.direct.JsonProvider
76777  *
76778  * <p>Provides for repetitive polling of the server at distinct {@link #interval intervals}.
76779  * The initial request for data originates from the client, and then is responded to by the
76780  * server.</p>
76781  * 
76782  * <p>All configurations for the PollingProvider should be generated by the server-side
76783  * API portion of the Ext.Direct stack.</p>
76784  *
76785  * <p>An instance of PollingProvider may be created directly via the new keyword or by simply
76786  * specifying <tt>type = 'polling'</tt>.  For example:</p>
76787  * <pre><code>
76788 var pollA = new Ext.direct.PollingProvider({
76789     type:'polling',
76790     url: 'php/pollA.php',
76791 });
76792 Ext.direct.Manager.addProvider(pollA);
76793 pollA.disconnect();
76794
76795 Ext.direct.Manager.addProvider(
76796     {
76797         type:'polling',
76798         url: 'php/pollB.php',
76799         id: 'pollB-provider'
76800     }
76801 );
76802 var pollB = Ext.direct.Manager.getProvider('pollB-provider');
76803  * </code></pre>
76804  */
76805 Ext.define('Ext.direct.PollingProvider', {
76806     
76807     /* Begin Definitions */
76808     
76809     extend: 'Ext.direct.JsonProvider',
76810     
76811     alias: 'direct.pollingprovider',
76812     
76813     uses: ['Ext.direct.ExceptionEvent'],
76814     
76815     requires: ['Ext.Ajax', 'Ext.util.DelayedTask'],
76816     
76817     /* End Definitions */
76818     
76819     /**
76820      * @cfg {Number} interval
76821      * How often to poll the server-side in milliseconds (defaults to <tt>3000</tt> - every
76822      * 3 seconds).
76823      */
76824     interval: 3000,
76825
76826     /**
76827      * @cfg {Object} baseParams An object containing properties which are to be sent as parameters
76828      * on every polling request
76829      */
76830     
76831     /**
76832      * @cfg {String/Function} url
76833      * The url which the PollingProvider should contact with each request. This can also be
76834      * an imported Ext.Direct method which will accept the baseParams as its only argument.
76835      */
76836
76837     // private
76838     constructor : function(config){
76839         this.callParent(arguments);
76840         this.addEvents(
76841             /**
76842              * @event beforepoll
76843              * Fired immediately before a poll takes place, an event handler can return false
76844              * in order to cancel the poll.
76845              * @param {Ext.direct.PollingProvider}
76846              */
76847             'beforepoll',            
76848             /**
76849              * @event poll
76850              * This event has not yet been implemented.
76851              * @param {Ext.direct.PollingProvider}
76852              */
76853             'poll'
76854         );
76855     },
76856
76857     // inherited
76858     isConnected: function(){
76859         return !!this.pollTask;
76860     },
76861
76862     /**
76863      * Connect to the server-side and begin the polling process. To handle each
76864      * response subscribe to the data event.
76865      */
76866     connect: function(){
76867         var me = this, url = me.url;
76868         
76869         if (url && !me.pollTask) {
76870             me.pollTask = Ext.TaskManager.start({
76871                 run: function(){
76872                     if (me.fireEvent('beforepoll', me) !== false) {
76873                         if (Ext.isFunction(url)) {
76874                             url(me.baseParams);
76875                         } else {
76876                             Ext.Ajax.request({
76877                                 url: url,
76878                                 callback: me.onData,
76879                                 scope: me,
76880                                 params: me.baseParams
76881                             });
76882                         }
76883                     }
76884                 },
76885                 interval: me.interval,
76886                 scope: me
76887             });
76888             me.fireEvent('connect', me);
76889         } else if (!url) {
76890             Ext.Error.raise('Error initializing PollingProvider, no url configured.');
76891         }
76892     },
76893
76894     /**
76895      * Disconnect from the server-side and stop the polling process. The disconnect
76896      * event will be fired on a successful disconnect.
76897      */
76898     disconnect: function(){
76899         var me = this;
76900         
76901         if (me.pollTask) {
76902             Ext.TaskManager.stop(me.pollTask);
76903             delete me.pollTask;
76904             me.fireEvent('disconnect', me);
76905         }
76906     },
76907
76908     // private
76909     onData: function(opt, success, response){
76910         var me = this, 
76911             i = 0, 
76912             len,
76913             events;
76914         
76915         if (success) {
76916             events = me.createEvents(response);
76917             for (len = events.length; i < len; ++i) {
76918                 me.fireEvent('data', me, events[i]);
76919             }
76920         } else {
76921             me.fireEvent('data', me, Ext.create('Ext.direct.ExceptionEvent', {
76922                 data: null,
76923                 code: Ext.direct.Manager.self.exceptions.TRANSPORT,
76924                 message: 'Unable to connect to the server.',
76925                 xhr: response
76926             }));
76927         }
76928     }
76929 });
76930 /**
76931  * Small utility class used internally to represent a Direct method.
76932  * Thi class is used internally.
76933  * @class Ext.direct.RemotingMethod
76934  * @ignore
76935  */
76936 Ext.define('Ext.direct.RemotingMethod', {
76937     
76938     constructor: function(config){
76939         var me = this,
76940             params = Ext.isDefined(config.params) ? config.params : config.len,
76941             name;
76942             
76943         me.name = config.name;
76944         me.formHandler = config.formHandler;
76945         if (Ext.isNumber(params)) {
76946             // given only the number of parameters
76947             me.len = params;
76948             me.ordered = true;
76949         } else {
76950             /*
76951              * Given an array of either
76952              * a) String
76953              * b) Objects with a name property. We may want to encode extra info in here later
76954              */
76955             me.params = [];
76956             Ext.each(params, function(param){
76957                 name = Ext.isObject(param) ? param.name : param;
76958                 me.params.push(name);
76959             });
76960         }
76961     },
76962     
76963     /**
76964      * Takes the arguments for the Direct function and splits the arguments
76965      * from the scope and the callback.
76966      * @param {Array} args The arguments passed to the direct call
76967      * @return {Object} An object with 3 properties, args, callback & scope.
76968      */
76969     getCallData: function(args){
76970         var me = this,
76971             data = null,
76972             len  = me.len,
76973             params = me.params,
76974             callback,
76975             scope,
76976             name;
76977             
76978         if (me.ordered) {
76979             callback = args[len];
76980             scope = args[len + 1];
76981             if (len !== 0) {
76982                 data = args.slice(0, len);
76983             }
76984         } else {
76985             data = Ext.apply({}, args[0]);
76986             callback = args[1];
76987             scope = args[2];
76988             
76989             // filter out any non-existent properties
76990             for (name in data) {
76991                 if (data.hasOwnProperty(name)) {
76992                     if (!Ext.Array.contains(params, name)) {
76993                         delete data[name];
76994                     }
76995                 }
76996             }
76997         }
76998         
76999         return {
77000             data: data,
77001             callback: callback,
77002             scope: scope    
77003         };
77004     }
77005 });
77006
77007 /**
77008  * @class Ext.direct.Transaction
77009  * @extends Object
77010  * <p>Supporting Class for Ext.Direct (not intended to be used directly).</p>
77011  * @constructor
77012  * @param {Object} config
77013  */
77014 Ext.define('Ext.direct.Transaction', {
77015     
77016     /* Begin Definitions */
77017    
77018     alias: 'direct.transaction',
77019     alternateClassName: 'Ext.Direct.Transaction',
77020    
77021     statics: {
77022         TRANSACTION_ID: 0
77023     },
77024    
77025     /* End Definitions */
77026    
77027     constructor: function(config){
77028         var me = this;
77029         
77030         Ext.apply(me, config);
77031         me.id = ++me.self.TRANSACTION_ID;
77032         me.retryCount = 0;
77033     },
77034    
77035     send: function(){
77036          this.provider.queueTransaction(this);
77037     },
77038
77039     retry: function(){
77040         this.retryCount++;
77041         this.send();
77042     },
77043
77044     getProvider: function(){
77045         return this.provider;
77046     }
77047 });
77048
77049 /**
77050  * @class Ext.direct.RemotingProvider
77051  * @extends Ext.direct.JsonProvider
77052  * 
77053  * <p>The {@link Ext.direct.RemotingProvider RemotingProvider} exposes access to
77054  * server side methods on the client (a remote procedure call (RPC) type of
77055  * connection where the client can initiate a procedure on the server).</p>
77056  * 
77057  * <p>This allows for code to be organized in a fashion that is maintainable,
77058  * while providing a clear path between client and server, something that is
77059  * not always apparent when using URLs.</p>
77060  * 
77061  * <p>To accomplish this the server-side needs to describe what classes and methods
77062  * are available on the client-side. This configuration will typically be
77063  * outputted by the server-side Ext.Direct stack when the API description is built.</p>
77064  */
77065 Ext.define('Ext.direct.RemotingProvider', {
77066     
77067     /* Begin Definitions */
77068    
77069     alias: 'direct.remotingprovider',
77070     
77071     extend: 'Ext.direct.JsonProvider', 
77072     
77073     requires: [
77074         'Ext.util.MixedCollection', 
77075         'Ext.util.DelayedTask', 
77076         'Ext.direct.Transaction',
77077         'Ext.direct.RemotingMethod'
77078     ],
77079    
77080     /* End Definitions */
77081    
77082    /**
77083      * @cfg {Object} actions
77084      * Object literal defining the server side actions and methods. For example, if
77085      * the Provider is configured with:
77086      * <pre><code>
77087 "actions":{ // each property within the 'actions' object represents a server side Class 
77088     "TestAction":[ // array of methods within each server side Class to be   
77089     {              // stubbed out on client
77090         "name":"doEcho", 
77091         "len":1            
77092     },{
77093         "name":"multiply",// name of method
77094         "len":2           // The number of parameters that will be used to create an
77095                           // array of data to send to the server side function.
77096                           // Ensure the server sends back a Number, not a String. 
77097     },{
77098         "name":"doForm",
77099         "formHandler":true, // direct the client to use specialized form handling method 
77100         "len":1
77101     }]
77102 }
77103      * </code></pre>
77104      * <p>Note that a Store is not required, a server method can be called at any time.
77105      * In the following example a <b>client side</b> handler is used to call the
77106      * server side method "multiply" in the server-side "TestAction" Class:</p>
77107      * <pre><code>
77108 TestAction.multiply(
77109     2, 4, // pass two arguments to server, so specify len=2
77110     // callback function after the server is called
77111     // result: the result returned by the server
77112     //      e: Ext.direct.RemotingEvent object
77113     function(result, e){
77114         var t = e.getTransaction();
77115         var action = t.action; // server side Class called
77116         var method = t.method; // server side method called
77117         if(e.status){
77118             var answer = Ext.encode(result); // 8
77119     
77120         }else{
77121             var msg = e.message; // failure message
77122         }
77123     }
77124 );
77125      * </code></pre>
77126      * In the example above, the server side "multiply" function will be passed two
77127      * arguments (2 and 4).  The "multiply" method should return the value 8 which will be
77128      * available as the <tt>result</tt> in the example above. 
77129      */
77130     
77131     /**
77132      * @cfg {String/Object} namespace
77133      * Namespace for the Remoting Provider (defaults to the browser global scope of <i>window</i>).
77134      * Explicitly specify the namespace Object, or specify a String to have a
77135      * {@link Ext#namespace namespace created} implicitly.
77136      */
77137     
77138     /**
77139      * @cfg {String} url
77140      * <b>Required<b>. The url to connect to the {@link Ext.direct.Manager} server-side router. 
77141      */
77142     
77143     /**
77144      * @cfg {String} enableUrlEncode
77145      * Specify which param will hold the arguments for the method.
77146      * Defaults to <tt>'data'</tt>.
77147      */
77148     
77149     /**
77150      * @cfg {Number/Boolean} enableBuffer
77151      * <p><tt>true</tt> or <tt>false</tt> to enable or disable combining of method
77152      * calls. If a number is specified this is the amount of time in milliseconds
77153      * to wait before sending a batched request (defaults to <tt>10</tt>).</p>
77154      * <br><p>Calls which are received within the specified timeframe will be
77155      * concatenated together and sent in a single request, optimizing the
77156      * application by reducing the amount of round trips that have to be made
77157      * to the server.</p>
77158      */
77159     enableBuffer: 10,
77160     
77161     /**
77162      * @cfg {Number} maxRetries
77163      * Number of times to re-attempt delivery on failure of a call. Defaults to <tt>1</tt>.
77164      */
77165     maxRetries: 1,
77166     
77167     /**
77168      * @cfg {Number} timeout
77169      * The timeout to use for each request. Defaults to <tt>undefined</tt>.
77170      */
77171     timeout: undefined,
77172     
77173     constructor : function(config){
77174         var me = this;
77175         me.callParent(arguments);
77176         me.addEvents(
77177             /**
77178              * @event beforecall
77179              * Fires immediately before the client-side sends off the RPC call.
77180              * By returning false from an event handler you can prevent the call from
77181              * executing.
77182              * @param {Ext.direct.RemotingProvider} provider
77183              * @param {Ext.direct.Transaction} transaction
77184              * @param {Object} meta The meta data
77185              */            
77186             'beforecall',            
77187             /**
77188              * @event call
77189              * Fires immediately after the request to the server-side is sent. This does
77190              * NOT fire after the response has come back from the call.
77191              * @param {Ext.direct.RemotingProvider} provider
77192              * @param {Ext.direct.Transaction} transaction
77193              * @param {Object} meta The meta data
77194              */            
77195             'call'
77196         );
77197         me.namespace = (Ext.isString(me.namespace)) ? Ext.ns(me.namespace) : me.namespace || window;
77198         me.transactions = Ext.create('Ext.util.MixedCollection');
77199         me.callBuffer = [];
77200     },
77201     
77202     /**
77203      * Initialize the API
77204      * @private
77205      */
77206     initAPI : function(){
77207         var actions = this.actions,
77208             namespace = this.namespace,
77209             action,
77210             cls,
77211             methods,
77212             i,
77213             len,
77214             method;
77215             
77216         for (action in actions) {
77217             cls = namespace[action];
77218             if (!cls) {
77219                 cls = namespace[action] = {};
77220             }
77221             methods = actions[action];
77222             
77223             for (i = 0, len = methods.length; i < len; ++i) {
77224                 method = Ext.create('Ext.direct.RemotingMethod', methods[i]);
77225                 cls[method.name] = this.createHandler(action, method);
77226             }
77227         }
77228     },
77229     
77230     /**
77231      * Create a handler function for a direct call.
77232      * @private
77233      * @param {String} action The action the call is for
77234      * @param {Object} method The details of the method
77235      * @return {Function} A JS function that will kick off the call
77236      */
77237     createHandler : function(action, method){
77238         var me = this,
77239             handler;
77240         
77241         if (!method.formHandler) {
77242             handler = function(){
77243                 me.configureRequest(action, method, Array.prototype.slice.call(arguments, 0));
77244             };
77245         } else {
77246             handler = function(form, callback, scope){
77247                 me.configureFormRequest(action, method, form, callback, scope);
77248             };
77249         }
77250         handler.directCfg = {
77251             action: action,
77252             method: method
77253         };
77254         return handler;
77255     },
77256     
77257     // inherit docs
77258     isConnected: function(){
77259         return !!this.connected;
77260     },
77261
77262     // inherit docs
77263     connect: function(){
77264         var me = this;
77265         
77266         if (me.url) {
77267             me.initAPI();
77268             me.connected = true;
77269             me.fireEvent('connect', me);
77270         } else if(!me.url) {
77271             Ext.Error.raise('Error initializing RemotingProvider, no url configured.');
77272         }
77273     },
77274
77275     // inherit docs
77276     disconnect: function(){
77277         var me = this;
77278         
77279         if (me.connected) {
77280             me.connected = false;
77281             me.fireEvent('disconnect', me);
77282         }
77283     },
77284     
77285     /**
77286      * Run any callbacks related to the transaction.
77287      * @private
77288      * @param {Ext.direct.Transaction} transaction The transaction
77289      * @param {Ext.direct.Event} event The event
77290      */
77291     runCallback: function(transaction, event){
77292         var funcName = event.status ? 'success' : 'failure',
77293             callback,
77294             result;
77295         
77296         if (transaction && transaction.callback) {
77297             callback = transaction.callback;
77298             result = Ext.isDefined(event.result) ? event.result : event.data;
77299         
77300             if (Ext.isFunction(callback)) {
77301                 callback(result, event);
77302             } else {
77303                 Ext.callback(callback[funcName], callback.scope, [result, event]);
77304                 Ext.callback(callback.callback, callback.scope, [result, event]);
77305             }
77306         }
77307     },
77308     
77309     /**
77310      * React to the ajax request being completed
77311      * @private
77312      */
77313     onData: function(options, success, response){
77314         var me = this,
77315             i = 0,
77316             len,
77317             events,
77318             event,
77319             transaction,
77320             transactions;
77321             
77322         if (success) {
77323             events = me.createEvents(response);
77324             for (len = events.length; i < len; ++i) {
77325                 event = events[i];
77326                 transaction = me.getTransaction(event);
77327                 me.fireEvent('data', me, event);
77328                 if (transaction) {
77329                     me.runCallback(transaction, event, true);
77330                     Ext.direct.Manager.removeTransaction(transaction);
77331                 }
77332             }
77333         } else {
77334             transactions = [].concat(options.transaction);
77335             for (len = transactions.length; i < len; ++i) {
77336                 transaction = me.getTransaction(transactions[i]);
77337                 if (transaction && transaction.retryCount < me.maxRetries) {
77338                     transaction.retry();
77339                 } else {
77340                     event = Ext.create('Ext.direct.ExceptionEvent', {
77341                         data: null,
77342                         transaction: transaction,
77343                         code: Ext.direct.Manager.self.exceptions.TRANSPORT,
77344                         message: 'Unable to connect to the server.',
77345                         xhr: response
77346                     });
77347                     me.fireEvent('data', me, event);
77348                     if (transaction) {
77349                         me.runCallback(transaction, event, false);
77350                         Ext.direct.Manager.removeTransaction(transaction);
77351                     }
77352                 }
77353             }
77354         }
77355     },
77356     
77357     /**
77358      * Get transaction from XHR options
77359      * @private
77360      * @param {Object} options The options sent to the Ajax request
77361      * @return {Ext.direct.Transaction} The transaction, null if not found
77362      */
77363     getTransaction: function(options){
77364         return options && options.tid ? Ext.direct.Manager.getTransaction(options.tid) : null;
77365     },
77366     
77367     /**
77368      * Configure a direct request
77369      * @private
77370      * @param {String} action The action being executed
77371      * @param {Object} method The being executed
77372      */
77373     configureRequest: function(action, method, args){
77374         var me = this,
77375             callData = method.getCallData(args),
77376             data = callData.data, 
77377             callback = callData.callback, 
77378             scope = callData.scope,
77379             transaction;
77380
77381         transaction = Ext.create('Ext.direct.Transaction', {
77382             provider: me,
77383             args: args,
77384             action: action,
77385             method: method.name,
77386             data: data,
77387             callback: scope && Ext.isFunction(callback) ? Ext.Function.bind(callback, scope) : callback
77388         });
77389
77390         if (me.fireEvent('beforecall', me, transaction, method) !== false) {
77391             Ext.direct.Manager.addTransaction(transaction);
77392             me.queueTransaction(transaction);
77393             me.fireEvent('call', me, transaction, method);
77394         }
77395     },
77396     
77397     /**
77398      * Gets the Ajax call info for a transaction
77399      * @private
77400      * @param {Ext.direct.Transaction} transaction The transaction
77401      * @return {Object} The call params
77402      */
77403     getCallData: function(transaction){
77404         return {
77405             action: transaction.action,
77406             method: transaction.method,
77407             data: transaction.data,
77408             type: 'rpc',
77409             tid: transaction.id
77410         };
77411     },
77412     
77413     /**
77414      * Sends a request to the server
77415      * @private
77416      * @param {Object/Array} data The data to send
77417      */
77418     sendRequest : function(data){
77419         var me = this,
77420             request = {
77421                 url: me.url,
77422                 callback: me.onData,
77423                 scope: me,
77424                 transaction: data,
77425                 timeout: me.timeout
77426             }, callData,
77427             enableUrlEncode = me.enableUrlEncode,
77428             i = 0,
77429             len,
77430             params;
77431             
77432
77433         if (Ext.isArray(data)) {
77434             callData = [];
77435             for (len = data.length; i < len; ++i) {
77436                 callData.push(me.getCallData(data[i]));
77437             }
77438         } else {
77439             callData = me.getCallData(data);
77440         }
77441
77442         if (enableUrlEncode) {
77443             params = {};
77444             params[Ext.isString(enableUrlEncode) ? enableUrlEncode : 'data'] = Ext.encode(callData);
77445             request.params = params;
77446         } else {
77447             request.jsonData = callData;
77448         }
77449         Ext.Ajax.request(request);
77450     },
77451     
77452     /**
77453      * Add a new transaction to the queue
77454      * @private
77455      * @param {Ext.direct.Transaction} transaction The transaction
77456      */
77457     queueTransaction: function(transaction){
77458         var me = this,
77459             enableBuffer = me.enableBuffer;
77460         
77461         if (transaction.form) {
77462             me.sendFormRequest(transaction);
77463             return;
77464         }
77465         
77466         me.callBuffer.push(transaction);
77467         if (enableBuffer) {
77468             if (!me.callTask) {
77469                 me.callTask = Ext.create('Ext.util.DelayedTask', me.combineAndSend, me);
77470             }
77471             me.callTask.delay(Ext.isNumber(enableBuffer) ? enableBuffer : 10);
77472         } else {
77473             me.combineAndSend();
77474         }
77475     },
77476     
77477     /**
77478      * Combine any buffered requests and send them off
77479      * @private
77480      */
77481     combineAndSend : function(){
77482         var buffer = this.callBuffer,
77483             len = buffer.length;
77484             
77485         if (len > 0) {
77486             this.sendRequest(len == 1 ? buffer[0] : buffer);
77487             this.callBuffer = [];
77488         }
77489     },
77490     
77491     /**
77492      * Configure a form submission request
77493      * @private
77494      * @param {String} action The action being executed
77495      * @param {Object} method The method being executed
77496      * @param {HTMLElement} form The form being submitted
77497      * @param {Function} callback (optional) A callback to run after the form submits
77498      * @param {Object} scope A scope to execute the callback in
77499      */
77500     configureFormRequest : function(action, method, form, callback, scope){
77501         var me = this,
77502             transaction = Ext.create('Ext.direct.Transaction', {
77503                 provider: me,
77504                 action: action,
77505                 method: method.name,
77506                 args: [form, callback, scope],
77507                 callback: scope && Ext.isFunction(callback) ? Ext.Function.bind(callback, scope) : callback,
77508                 isForm: true
77509             }),
77510             isUpload,
77511             params;
77512
77513         if (me.fireEvent('beforecall', me, transaction, method) !== false) {
77514             Ext.direct.Manager.addTransaction(transaction);
77515             isUpload = String(form.getAttribute("enctype")).toLowerCase() == 'multipart/form-data';
77516             
77517             params = {
77518                 extTID: transaction.id,
77519                 extAction: action,
77520                 extMethod: method.name,
77521                 extType: 'rpc',
77522                 extUpload: String(isUpload)
77523             };
77524             
77525             // change made from typeof callback check to callback.params
77526             // to support addl param passing in DirectSubmit EAC 6/2
77527             Ext.apply(transaction, {
77528                 form: Ext.getDom(form),
77529                 isUpload: isUpload,
77530                 params: callback && Ext.isObject(callback.params) ? Ext.apply(params, callback.params) : params
77531             });
77532             me.fireEvent('call', me, transaction, method);
77533             me.sendFormRequest(transaction);
77534         }
77535     },
77536     
77537     /**
77538      * Sends a form request
77539      * @private
77540      * @param {Ext.direct.Transaction} transaction The transaction to send
77541      */
77542     sendFormRequest: function(transaction){
77543         Ext.Ajax.request({
77544             url: this.url,
77545             params: transaction.params,
77546             callback: this.onData,
77547             scope: this,
77548             form: transaction.form,
77549             isUpload: transaction.isUpload,
77550             transaction: transaction
77551         });
77552     }
77553     
77554 });
77555
77556 /*
77557  * @class Ext.draw.Matrix
77558  * @private
77559  */
77560 Ext.define('Ext.draw.Matrix', {
77561
77562     /* Begin Definitions */
77563
77564     requires: ['Ext.draw.Draw'],
77565
77566     /* End Definitions */
77567
77568     constructor: function(a, b, c, d, e, f) {
77569         if (a != null) {
77570             this.matrix = [[a, c, e], [b, d, f], [0, 0, 1]];
77571         }
77572         else {
77573             this.matrix = [[1, 0, 0], [0, 1, 0], [0, 0, 1]];
77574         }
77575     },
77576
77577     add: function(a, b, c, d, e, f) {
77578         var me = this,
77579             out = [[], [], []],
77580             matrix = [[a, c, e], [b, d, f], [0, 0, 1]],
77581             x,
77582             y,
77583             z,
77584             res;
77585
77586         for (x = 0; x < 3; x++) {
77587             for (y = 0; y < 3; y++) {
77588                 res = 0;
77589                 for (z = 0; z < 3; z++) {
77590                     res += me.matrix[x][z] * matrix[z][y];
77591                 }
77592                 out[x][y] = res;
77593             }
77594         }
77595         me.matrix = out;
77596     },
77597
77598     prepend: function(a, b, c, d, e, f) {
77599         var me = this,
77600             out = [[], [], []],
77601             matrix = [[a, c, e], [b, d, f], [0, 0, 1]],
77602             x,
77603             y,
77604             z,
77605             res;
77606
77607         for (x = 0; x < 3; x++) {
77608             for (y = 0; y < 3; y++) {
77609                 res = 0;
77610                 for (z = 0; z < 3; z++) {
77611                     res += matrix[x][z] * me.matrix[z][y];
77612                 }
77613                 out[x][y] = res;
77614             }
77615         }
77616         me.matrix = out;
77617     },
77618
77619     invert: function() {
77620         var matrix = this.matrix,
77621             a = matrix[0][0],
77622             b = matrix[1][0],
77623             c = matrix[0][1],
77624             d = matrix[1][1],
77625             e = matrix[0][2],
77626             f = matrix[1][2],
77627             x = a * d - b * c;
77628         return new Ext.draw.Matrix(d / x, -b / x, -c / x, a / x, (c * f - d * e) / x, (b * e - a * f) / x);
77629     },
77630
77631     clone: function() {
77632         var matrix = this.matrix,
77633             a = matrix[0][0],
77634             b = matrix[1][0],
77635             c = matrix[0][1],
77636             d = matrix[1][1],
77637             e = matrix[0][2],
77638             f = matrix[1][2];
77639         return new Ext.draw.Matrix(a, b, c, d, e, f);
77640     },
77641
77642     translate: function(x, y) {
77643         this.prepend(1, 0, 0, 1, x, y);
77644     },
77645
77646     scale: function(x, y, cx, cy) {
77647         var me = this;
77648         if (y == null) {
77649             y = x;
77650         }
77651         me.add(1, 0, 0, 1, cx, cy);
77652         me.add(x, 0, 0, y, 0, 0);
77653         me.add(1, 0, 0, 1, -cx, -cy);
77654     },
77655
77656     rotate: function(a, x, y) {
77657         a = Ext.draw.Draw.rad(a);
77658         var me = this,
77659             cos = +Math.cos(a).toFixed(9),
77660             sin = +Math.sin(a).toFixed(9);
77661         me.add(cos, sin, -sin, cos, x, y);
77662         me.add(1, 0, 0, 1, -x, -y);
77663     },
77664
77665     x: function(x, y) {
77666         var matrix = this.matrix;
77667         return x * matrix[0][0] + y * matrix[0][1] + matrix[0][2];
77668     },
77669
77670     y: function(x, y) {
77671         var matrix = this.matrix;
77672         return x * matrix[1][0] + y * matrix[1][1] + matrix[1][2];
77673     },
77674
77675     get: function(i, j) {
77676         return + this.matrix[i][j].toFixed(4);
77677     },
77678
77679     toString: function() {
77680         var me = this;
77681         return [me.get(0, 0), me.get(0, 1), me.get(1, 0), me.get(1, 1), 0, 0].join();
77682     },
77683
77684     toSvg: function() {
77685         var me = this;
77686         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() + ")";
77687     },
77688
77689     toFilter: function() {
77690         var me = this;
77691         return "progid:DXImageTransform.Microsoft.Matrix(M11=" + me.get(0, 0) +
77692             ", M12=" + me.get(0, 1) + ", M21=" + me.get(1, 0) + ", M22=" + me.get(1, 1) +
77693             ", Dx=" + me.get(0, 2) + ", Dy=" + me.get(1, 2) + ")";
77694     },
77695
77696     offset: function() {
77697         var matrix = this.matrix;
77698         return [matrix[0][2].toFixed(4), matrix[1][2].toFixed(4)];
77699     },
77700
77701     // Split matrix into Translate Scale, Shear, and Rotate
77702     split: function () {
77703         function norm(a) {
77704             return a[0] * a[0] + a[1] * a[1];
77705         }
77706         function normalize(a) {
77707             var mag = Math.sqrt(norm(a));
77708             a[0] /= mag;
77709             a[1] /= mag;
77710         }
77711         var matrix = this.matrix,
77712             out = {
77713                 translateX: matrix[0][2],
77714                 translateY: matrix[1][2]
77715             },
77716             row;
77717
77718         // scale and shear
77719         row = [[matrix[0][0], matrix[0][1]], [matrix[1][1], matrix[1][1]]];
77720         out.scaleX = Math.sqrt(norm(row[0]));
77721         normalize(row[0]);
77722
77723         out.shear = row[0][0] * row[1][0] + row[0][1] * row[1][1];
77724         row[1] = [row[1][0] - row[0][0] * out.shear, row[1][1] - row[0][1] * out.shear];
77725
77726         out.scaleY = Math.sqrt(norm(row[1]));
77727         normalize(row[1]);
77728         out.shear /= out.scaleY;
77729
77730         // rotation
77731         out.rotate = Math.asin(-row[0][1]);
77732
77733         out.isSimple = !+out.shear.toFixed(9) && (out.scaleX.toFixed(9) == out.scaleY.toFixed(9) || !out.rotate);
77734
77735         return out;
77736     }
77737 });
77738 // private - DD implementation for Panels
77739 Ext.define('Ext.draw.SpriteDD', {
77740     extend: 'Ext.dd.DragSource',
77741
77742     constructor : function(sprite, cfg){
77743         var me = this,
77744             el = sprite.el;
77745         me.sprite = sprite;
77746         me.el = el;
77747         me.dragData = {el: el, sprite: sprite};
77748         me.callParent([el, cfg]);
77749         me.sprite.setStyle('cursor', 'move');
77750     },
77751
77752     showFrame: Ext.emptyFn,
77753     createFrame : Ext.emptyFn,
77754
77755     getDragEl : function(e){
77756         return this.el;
77757     },
77758     
77759     getRegion: function() {
77760         var me = this,
77761             el = me.el,
77762             pos, x1, x2, y1, y2, t, r, b, l, bbox, sprite;
77763         
77764         sprite = me.sprite;
77765         bbox = sprite.getBBox();
77766         
77767         try {
77768             pos = Ext.core.Element.getXY(el);
77769         } catch (e) { }
77770
77771         if (!pos) {
77772             return null;
77773         }
77774
77775         x1 = pos[0];
77776         x2 = x1 + bbox.width;
77777         y1 = pos[1];
77778         y2 = y1 + bbox.height;
77779         
77780         return Ext.create('Ext.util.Region', y1, x2, y2, x1);
77781     },
77782
77783     /*
77784       TODO(nico): Cumulative translations in VML are handled
77785       differently than in SVG. While in SVG we specify the translation
77786       relative to the original x, y position attributes, in VML the translation
77787       is a delta between the last position of the object (modified by the last
77788       translation) and the new one.
77789       
77790       In VML the translation alters the position
77791       of the object, we should change that or alter the SVG impl.
77792     */
77793      
77794     startDrag: function(x, y) {
77795         var me = this,
77796             attr = me.sprite.attr,
77797             trans = attr.translation;
77798         if (me.sprite.vml) {
77799             me.prevX = x + attr.x;
77800             me.prevY = y + attr.y;
77801         } else {
77802             me.prevX = x - trans.x;
77803             me.prevY = y - trans.y;
77804         }
77805     },
77806
77807     onDrag: function(e) {
77808         var xy = e.getXY(),
77809             me = this,
77810             sprite = me.sprite,
77811             attr = sprite.attr;
77812         me.translateX = xy[0] - me.prevX;
77813         me.translateY = xy[1] - me.prevY;
77814         sprite.setAttributes({
77815             translate: {
77816                 x: me.translateX,
77817                 y: me.translateY
77818             }
77819         }, true);
77820         if (sprite.vml) {
77821             me.prevX = xy[0] + attr.x || 0;
77822             me.prevY = xy[1] + attr.y || 0;
77823         }
77824     }
77825 });
77826 /**
77827  * @class Ext.draw.Sprite
77828  * @extends Object
77829  *
77830  * A Sprite is an object rendered in a Drawing surface. There are different options and types of sprites.
77831  * The configuration of a Sprite is an object with the following properties:
77832  *
77833  * - **type** - (String) The type of the sprite. Possible options are 'circle', 'path', 'rect', 'text', 'square'. 
77834  * - **width** - (Number) Used in rectangle sprites, the width of the rectangle.
77835  * - **height** - (Number) Used in rectangle sprites, the height of the rectangle.
77836  * - **size** - (Number) Used in square sprites, the dimension of the square.
77837  * - **radius** - (Number) Used in circle sprites, the radius of the circle.
77838  * - **x** - (Number) The position along the x-axis.
77839  * - **y** - (Number) The position along the y-axis.
77840  * - **path** - (Array) Used in path sprites, the path of the sprite written in SVG-like path syntax.
77841  * - **opacity** - (Number) The opacity of the sprite.
77842  * - **fill** - (String) The fill color.
77843  * - **stroke** - (String) The stroke color.
77844  * - **stroke-width** - (Number) The width of the stroke.
77845  * - **font** - (String) Used with text type sprites. The full font description. Uses the same syntax as the CSS `font` parameter.
77846  * - **text** - (String) Used with text type sprites. The text itself.
77847  * 
77848  * Additionally there are three transform objects that can be set with `setAttributes` which are `translate`, `rotate` and
77849  * `scale`.
77850  * 
77851  * For translate, the configuration object contains x and y attributes that indicate where to
77852  * translate the object. For example:
77853  * 
77854  *     sprite.setAttributes({
77855  *       translate: {
77856  *        x: 10,
77857  *        y: 10
77858  *       }
77859  *     }, true);
77860  * 
77861  * For rotation, the configuration object contains x and y attributes for the center of the rotation (which are optional),
77862  * and a `degrees` attribute that specifies the rotation in degrees. For example:
77863  * 
77864  *     sprite.setAttributes({
77865  *       rotate: {
77866  *        degrees: 90
77867  *       }
77868  *     }, true);
77869  * 
77870  * For scaling, the configuration object contains x and y attributes for the x-axis and y-axis scaling. For example:
77871  * 
77872  *     sprite.setAttributes({
77873  *       scale: {
77874  *        x: 10,
77875  *        y: 3
77876  *       }
77877  *     }, true);
77878  *
77879  * Sprites can be created with a reference to a {@link Ext.draw.Surface}
77880  *
77881  *      var drawComponent = Ext.create('Ext.draw.Component', options here...);
77882  *
77883  *      var sprite = Ext.create('Ext.draw.Sprite', {
77884  *          type: 'circle',
77885  *          fill: '#ff0',
77886  *          surface: drawComponent.surface,
77887  *          radius: 5
77888  *      });
77889  *
77890  * Sprites can also be added to the surface as a configuration object:
77891  *
77892  *      var sprite = drawComponent.surface.add({
77893  *          type: 'circle',
77894  *          fill: '#ff0',
77895  *          radius: 5
77896  *      });
77897  *
77898  * In order to properly apply properties and render the sprite we have to
77899  * `show` the sprite setting the option `redraw` to `true`:
77900  *
77901  *      sprite.show(true);
77902  *
77903  * The constructor configuration object of the Sprite can also be used and passed into the {@link Ext.draw.Surface}
77904  * add method to append a new sprite to the canvas. For example:
77905  *
77906  *     drawComponent.surface.add({
77907  *         type: 'circle',
77908  *         fill: '#ffc',
77909  *         radius: 100,
77910  *         x: 100,
77911  *         y: 100
77912  *     });
77913  */
77914 Ext.define('Ext.draw.Sprite', {
77915     /* Begin Definitions */
77916
77917     mixins: {
77918         observable: 'Ext.util.Observable',
77919         animate: 'Ext.util.Animate'
77920     },
77921
77922     requires: ['Ext.draw.SpriteDD'],
77923
77924     /* End Definitions */
77925
77926     dirty: false,
77927     dirtyHidden: false,
77928     dirtyTransform: false,
77929     dirtyPath: true,
77930     dirtyFont: true,
77931     zIndexDirty: true,
77932     isSprite: true,
77933     zIndex: 0,
77934     fontProperties: [
77935         'font',
77936         'font-size',
77937         'font-weight',
77938         'font-style',
77939         'font-family',
77940         'text-anchor',
77941         'text'
77942     ],
77943     pathProperties: [
77944         'x',
77945         'y',
77946         'd',
77947         'path',
77948         'height',
77949         'width',
77950         'radius',
77951         'r',
77952         'rx',
77953         'ry',
77954         'cx',
77955         'cy'
77956     ],
77957     constructor: function(config) {
77958         var me = this;
77959         config = config || {};
77960         me.id = Ext.id(null, 'ext-sprite-');
77961         me.transformations = [];
77962         Ext.copyTo(this, config, 'surface,group,type,draggable');
77963         //attribute bucket
77964         me.bbox = {};
77965         me.attr = {
77966             zIndex: 0,
77967             translation: {
77968                 x: null,
77969                 y: null
77970             },
77971             rotation: {
77972                 degrees: null,
77973                 x: null,
77974                 y: null
77975             },
77976             scaling: {
77977                 x: null,
77978                 y: null,
77979                 cx: null,
77980                 cy: null
77981             }
77982         };
77983         //delete not bucket attributes
77984         delete config.surface;
77985         delete config.group;
77986         delete config.type;
77987         delete config.draggable;
77988         me.setAttributes(config);
77989         me.addEvents(
77990             'beforedestroy',
77991             'destroy',
77992             'render',
77993             'mousedown',
77994             'mouseup',
77995             'mouseover',
77996             'mouseout',
77997             'mousemove',
77998             'click'
77999         );
78000         me.mixins.observable.constructor.apply(this, arguments);
78001     },
78002
78003     /**
78004      * <p>If this Sprite is configured {@link #draggable}, this property will contain
78005      * an instance of {@link Ext.dd.DragSource} which handles dragging the Sprite.</p>
78006      * The developer must provide implementations of the abstract methods of {@link Ext.dd.DragSource}
78007      * in order to supply behaviour for each stage of the drag/drop process. See {@link #draggable}.
78008      * @type Ext.dd.DragSource.
78009      * @property dd
78010      */
78011     initDraggable: function() {
78012         var me = this;
78013         me.draggable = true;
78014         //create element if it doesn't exist.
78015         if (!me.el) {
78016             me.surface.createSprite(me);
78017         }
78018         me.dd = Ext.create('Ext.draw.SpriteDD', me, Ext.isBoolean(me.draggable) ? null : me.draggable);
78019         me.on('beforedestroy', me.dd.destroy, me.dd);
78020     },
78021
78022     /**
78023      * Change the attributes of the sprite.
78024      * @param {Object} attrs attributes to be changed on the sprite.
78025      * @param {Boolean} redraw Flag to immediatly draw the change.
78026      * @return {Ext.draw.Sprite} this
78027      */
78028     setAttributes: function(attrs, redraw) {
78029         var me = this,
78030             fontProps = me.fontProperties,
78031             fontPropsLength = fontProps.length,
78032             pathProps = me.pathProperties,
78033             pathPropsLength = pathProps.length,
78034             hasSurface = !!me.surface,
78035             custom = hasSurface && me.surface.customAttributes || {},
78036             spriteAttrs = me.attr,
78037             attr, i, translate, translation, rotate, rotation, scale, scaling;
78038
78039         for (attr in custom) {
78040             if (attrs.hasOwnProperty(attr) && typeof custom[attr] == "function") {
78041                 Ext.apply(attrs, custom[attr].apply(me, [].concat(attrs[attr])));
78042             }
78043         }
78044
78045         // Flag a change in hidden
78046         if (!!attrs.hidden !== !!spriteAttrs.hidden) {
78047             me.dirtyHidden = true;
78048         }
78049
78050         // Flag path change
78051         for (i = 0; i < pathPropsLength; i++) {
78052             attr = pathProps[i];
78053             if (attr in attrs && attrs[attr] !== spriteAttrs[attr]) {
78054                 me.dirtyPath = true;
78055                 break;
78056             }
78057         }
78058
78059         // Flag zIndex change
78060         if ('zIndex' in attrs) {
78061             me.zIndexDirty = true;
78062         }
78063
78064         // Flag font/text change
78065         for (i = 0; i < fontPropsLength; i++) {
78066             attr = fontProps[i];
78067             if (attr in attrs && attrs[attr] !== spriteAttrs[attr]) {
78068                 me.dirtyFont = true;
78069                 break;
78070             }
78071         }
78072
78073         translate = attrs.translate;
78074         translation = spriteAttrs.translation;
78075         if (translate) {
78076             if ((translate.x && translate.x !== translation.x) ||
78077                 (translate.y && translate.y !== translation.y)) {
78078                 Ext.apply(translation, translate);
78079                 me.dirtyTransform = true;
78080             }
78081             delete attrs.translate;
78082         }
78083
78084         rotate = attrs.rotate;
78085         rotation = spriteAttrs.rotation;
78086         if (rotate) {
78087             if ((rotate.x && rotate.x !== rotation.x) || 
78088                 (rotate.y && rotate.y !== rotation.y) ||
78089                 (rotate.degrees && rotate.degrees !== rotation.degrees)) {
78090                 Ext.apply(rotation, rotate);
78091                 me.dirtyTransform = true;
78092             }
78093             delete attrs.rotate;
78094         }
78095
78096         scale = attrs.scale;
78097         scaling = spriteAttrs.scaling;
78098         if (scale) {
78099             if ((scale.x && scale.x !== scaling.x) || 
78100                 (scale.y && scale.y !== scaling.y) ||
78101                 (scale.cx && scale.cx !== scaling.cx) ||
78102                 (scale.cy && scale.cy !== scaling.cy)) {
78103                 Ext.apply(scaling, scale);
78104                 me.dirtyTransform = true;
78105             }
78106             delete attrs.scale;
78107         }
78108
78109         Ext.apply(spriteAttrs, attrs);
78110         me.dirty = true;
78111
78112         if (redraw === true && hasSurface) {
78113             me.redraw();
78114         }
78115         return this;
78116     },
78117
78118     /**
78119      * Retrieve the bounding box of the sprite. This will be returned as an object with x, y, width, and height properties.
78120      * @return {Object} bbox
78121      */
78122     getBBox: function() {
78123         return this.surface.getBBox(this);
78124     },
78125     
78126     setText: function(text) {
78127         return this.surface.setText(this, text);
78128     },
78129
78130     /**
78131      * Hide the sprite.
78132      * @param {Boolean} redraw Flag to immediatly draw the change.
78133      * @return {Ext.draw.Sprite} this
78134      */
78135     hide: function(redraw) {
78136         this.setAttributes({
78137             hidden: true
78138         }, redraw);
78139         return this;
78140     },
78141
78142     /**
78143      * Show the sprite.
78144      * @param {Boolean} redraw Flag to immediatly draw the change.
78145      * @return {Ext.draw.Sprite} this
78146      */
78147     show: function(redraw) {
78148         this.setAttributes({
78149             hidden: false
78150         }, redraw);
78151         return this;
78152     },
78153
78154     /**
78155      * Remove the sprite.
78156      */
78157     remove: function() {
78158         if (this.surface) {
78159             this.surface.remove(this);
78160             return true;
78161         }
78162         return false;
78163     },
78164
78165     onRemove: function() {
78166         this.surface.onRemove(this);
78167     },
78168
78169     /**
78170      * Removes the sprite and clears all listeners.
78171      */
78172     destroy: function() {
78173         var me = this;
78174         if (me.fireEvent('beforedestroy', me) !== false) {
78175             me.remove();
78176             me.surface.onDestroy(me);
78177             me.clearListeners();
78178             me.fireEvent('destroy');
78179         }
78180     },
78181
78182     /**
78183      * Redraw the sprite.
78184      * @return {Ext.draw.Sprite} this
78185      */
78186     redraw: function() {
78187         this.surface.renderItem(this);
78188         return this;
78189     },
78190
78191     /**
78192      * Wrapper for setting style properties, also takes single object parameter of multiple styles.
78193      * @param {String/Object} property The style property to be set, or an object of multiple styles.
78194      * @param {String} value (optional) The value to apply to the given property, or null if an object was passed.
78195      * @return {Ext.draw.Sprite} this
78196      */
78197     setStyle: function() {
78198         this.el.setStyle.apply(this.el, arguments);
78199         return this;
78200     },
78201
78202     /**
78203      * Adds one or more CSS classes to the element. Duplicate classes are automatically filtered out.  Note this method
78204      * is severly limited in VML.
78205      * @param {String/Array} className The CSS class to add, or an array of classes
78206      * @return {Ext.draw.Sprite} this
78207      */
78208     addCls: function(obj) {
78209         this.surface.addCls(this, obj);
78210         return this;
78211     },
78212
78213     /**
78214      * Removes one or more CSS classes from the element.
78215      * @param {String/Array} className The CSS class to remove, or an array of classes.  Note this method
78216      * is severly limited in VML.
78217      * @return {Ext.draw.Sprite} this
78218      */
78219     removeCls: function(obj) {
78220         this.surface.removeCls(this, obj);
78221         return this;
78222     }
78223 });
78224
78225 /**
78226  * @class Ext.draw.engine.Svg
78227  * @extends Ext.draw.Surface
78228  * Provides specific methods to draw with SVG.
78229  */
78230 Ext.define('Ext.draw.engine.Svg', {
78231
78232     /* Begin Definitions */
78233
78234     extend: 'Ext.draw.Surface',
78235
78236     requires: ['Ext.draw.Draw', 'Ext.draw.Sprite', 'Ext.draw.Matrix', 'Ext.core.Element'],
78237
78238     /* End Definitions */
78239
78240     engine: 'Svg',
78241
78242     trimRe: /^\s+|\s+$/g,
78243     spacesRe: /\s+/,
78244     xlink: "http:/" + "/www.w3.org/1999/xlink",
78245
78246     translateAttrs: {
78247         radius: "r",
78248         radiusX: "rx",
78249         radiusY: "ry",
78250         path: "d",
78251         lineWidth: "stroke-width",
78252         fillOpacity: "fill-opacity",
78253         strokeOpacity: "stroke-opacity",
78254         strokeLinejoin: "stroke-linejoin"
78255     },
78256
78257     minDefaults: {
78258         circle: {
78259             cx: 0,
78260             cy: 0,
78261             r: 0,
78262             fill: "none",
78263             stroke: null,
78264             "stroke-width": null,
78265             opacity: null,
78266             "fill-opacity": null,
78267             "stroke-opacity": null
78268         },
78269         ellipse: {
78270             cx: 0,
78271             cy: 0,
78272             rx: 0,
78273             ry: 0,
78274             fill: "none",
78275             stroke: null,
78276             "stroke-width": null,
78277             opacity: null,
78278             "fill-opacity": null,
78279             "stroke-opacity": null
78280         },
78281         rect: {
78282             x: 0,
78283             y: 0,
78284             width: 0,
78285             height: 0,
78286             rx: 0,
78287             ry: 0,
78288             fill: "none",
78289             stroke: null,
78290             "stroke-width": null,
78291             opacity: null,
78292             "fill-opacity": null,
78293             "stroke-opacity": null
78294         },
78295         text: {
78296             x: 0,
78297             y: 0,
78298             "text-anchor": "start",
78299             "font-family": null,
78300             "font-size": null,
78301             "font-weight": null,
78302             "font-style": null,
78303             fill: "#000",
78304             stroke: null,
78305             "stroke-width": null,
78306             opacity: null,
78307             "fill-opacity": null,
78308             "stroke-opacity": null
78309         },
78310         path: {
78311             d: "M0,0",
78312             fill: "none",
78313             stroke: null,
78314             "stroke-width": null,
78315             opacity: null,
78316             "fill-opacity": null,
78317             "stroke-opacity": null
78318         },
78319         image: {
78320             x: 0,
78321             y: 0,
78322             width: 0,
78323             height: 0,
78324             preserveAspectRatio: "none",
78325             opacity: null
78326         }
78327     },
78328
78329     createSvgElement: function(type, attrs) {
78330         var el = this.domRef.createElementNS("http:/" + "/www.w3.org/2000/svg", type),
78331             key;
78332         if (attrs) {
78333             for (key in attrs) {
78334                 el.setAttribute(key, String(attrs[key]));
78335             }
78336         }
78337         return el;
78338     },
78339
78340     createSpriteElement: function(sprite) {
78341         // Create svg element and append to the DOM.
78342         var el = this.createSvgElement(sprite.type);
78343         el.id = sprite.id;
78344         if (el.style) {
78345             el.style.webkitTapHighlightColor = "rgba(0,0,0,0)";
78346         }
78347         sprite.el = Ext.get(el);
78348         this.applyZIndex(sprite); //performs the insertion
78349         sprite.matrix = Ext.create('Ext.draw.Matrix');
78350         sprite.bbox = {
78351             plain: 0,
78352             transform: 0
78353         };
78354         sprite.fireEvent("render", sprite);
78355         return el;
78356     },
78357
78358     getBBox: function (sprite, isWithoutTransform) {
78359         var realPath = this["getPath" + sprite.type](sprite);
78360         if (isWithoutTransform) {
78361             sprite.bbox.plain = sprite.bbox.plain || Ext.draw.Draw.pathDimensions(realPath);
78362             return sprite.bbox.plain;
78363         }
78364         sprite.bbox.transform = sprite.bbox.transform || Ext.draw.Draw.pathDimensions(Ext.draw.Draw.mapPath(realPath, sprite.matrix));
78365         return sprite.bbox.transform;
78366     },
78367     
78368     getBBoxText: function (sprite) {
78369         var bbox = {},
78370             bb, height, width, i, ln, el;
78371
78372         if (sprite && sprite.el) {
78373             el = sprite.el.dom;
78374             try {
78375                 bbox = el.getBBox();
78376                 return bbox;
78377             } catch(e) {
78378                 // Firefox 3.0.x plays badly here
78379             }
78380             bbox = {x: bbox.x, y: Infinity, width: 0, height: 0};
78381             ln = el.getNumberOfChars();
78382             for (i = 0; i < ln; i++) {
78383                 bb = el.getExtentOfChar(i);
78384                 bbox.y = Math.min(bb.y, bbox.y);
78385                 height = bb.y + bb.height - bbox.y;
78386                 bbox.height = Math.max(bbox.height, height);
78387                 width = bb.x + bb.width - bbox.x;
78388                 bbox.width = Math.max(bbox.width, width);
78389             }
78390             return bbox;
78391         }
78392     },
78393
78394     hide: function() {
78395         Ext.get(this.el).hide();
78396     },
78397
78398     show: function() {
78399         Ext.get(this.el).show();
78400     },
78401
78402     hidePrim: function(sprite) {
78403         this.addCls(sprite, Ext.baseCSSPrefix + 'hide-visibility');
78404     },
78405
78406     showPrim: function(sprite) {
78407         this.removeCls(sprite, Ext.baseCSSPrefix + 'hide-visibility');
78408     },
78409
78410     getDefs: function() {
78411         return this._defs || (this._defs = this.createSvgElement("defs"));
78412     },
78413
78414     transform: function(sprite) {
78415         var me = this,
78416             matrix = Ext.create('Ext.draw.Matrix'),
78417             transforms = sprite.transformations,
78418             transformsLength = transforms.length,
78419             i = 0,
78420             transform, type;
78421             
78422         for (; i < transformsLength; i++) {
78423             transform = transforms[i];
78424             type = transform.type;
78425             if (type == "translate") {
78426                 matrix.translate(transform.x, transform.y);
78427             }
78428             else if (type == "rotate") {
78429                 matrix.rotate(transform.degrees, transform.x, transform.y);
78430             }
78431             else if (type == "scale") {
78432                 matrix.scale(transform.x, transform.y, transform.centerX, transform.centerY);
78433             }
78434         }
78435         sprite.matrix = matrix;
78436         sprite.el.set({transform: matrix.toSvg()});
78437     },
78438
78439     setSize: function(w, h) {
78440         var me = this,
78441             el = me.el;
78442         
78443         w = +w || me.width;
78444         h = +h || me.height;
78445         me.width = w;
78446         me.height = h;
78447
78448         el.setSize(w, h);
78449         el.set({
78450             width: w,
78451             height: h
78452         });
78453         me.callParent([w, h]);
78454     },
78455
78456     /**
78457      * Get the region for the surface's canvas area
78458      * @returns {Ext.util.Region}
78459      */
78460     getRegion: function() {
78461         // Mozilla requires using the background rect because the svg element returns an
78462         // incorrect region. Webkit gives no region for the rect and must use the svg element.
78463         var svgXY = this.el.getXY(),
78464             rectXY = this.bgRect.getXY(),
78465             max = Math.max,
78466             x = max(svgXY[0], rectXY[0]),
78467             y = max(svgXY[1], rectXY[1]);
78468         return {
78469             left: x,
78470             top: y,
78471             right: x + this.width,
78472             bottom: y + this.height
78473         };
78474     },
78475
78476     onRemove: function(sprite) {
78477         if (sprite.el) {
78478             sprite.el.remove();
78479             delete sprite.el;
78480         }
78481         this.callParent(arguments);
78482     },
78483     
78484     setViewBox: function(x, y, width, height) {
78485         if (isFinite(x) && isFinite(y) && isFinite(width) && isFinite(height)) {
78486             this.callParent(arguments);
78487             this.el.dom.setAttribute("viewBox", [x, y, width, height].join(" "));
78488         }
78489     },
78490
78491     render: function (container) {
78492         var me = this;
78493         if (!me.el) {
78494             var width = me.width || 10,
78495                 height = me.height || 10,
78496                 el = me.createSvgElement('svg', {
78497                     xmlns: "http:/" + "/www.w3.org/2000/svg",
78498                     version: 1.1,
78499                     width: width,
78500                     height: height
78501                 }),
78502                 defs = me.getDefs(),
78503
78504                 // Create a rect that is always the same size as the svg root; this serves 2 purposes:
78505                 // (1) It allows mouse events to be fired over empty areas in Webkit, and (2) we can
78506                 // use it rather than the svg element for retrieving the correct client rect of the
78507                 // surface in Mozilla (see https://bugzilla.mozilla.org/show_bug.cgi?id=530985)
78508                 bgRect = me.createSvgElement("rect", {
78509                     width: "100%",
78510                     height: "100%",
78511                     fill: "#000",
78512                     stroke: "none",
78513                     opacity: 0
78514                 }),
78515                 webkitRect;
78516             
78517                 if (Ext.isSafari3) {
78518                     // Rect that we will show/hide to fix old WebKit bug with rendering issues.
78519                     webkitRect = me.createSvgElement("rect", {
78520                         x: -10,
78521                         y: -10,
78522                         width: "110%",
78523                         height: "110%",
78524                         fill: "none",
78525                         stroke: "#000"
78526                     });
78527                 }
78528             el.appendChild(defs);
78529             if (Ext.isSafari3) {
78530                 el.appendChild(webkitRect);
78531             }
78532             el.appendChild(bgRect);
78533             container.appendChild(el);
78534             me.el = Ext.get(el);
78535             me.bgRect = Ext.get(bgRect);
78536             if (Ext.isSafari3) {
78537                 me.webkitRect = Ext.get(webkitRect);
78538                 me.webkitRect.hide();
78539             }
78540             me.el.on({
78541                 scope: me,
78542                 mouseup: me.onMouseUp,
78543                 mousedown: me.onMouseDown,
78544                 mouseover: me.onMouseOver,
78545                 mouseout: me.onMouseOut,
78546                 mousemove: me.onMouseMove,
78547                 mouseenter: me.onMouseEnter,
78548                 mouseleave: me.onMouseLeave,
78549                 click: me.onClick
78550             });
78551         }
78552         me.renderAll();
78553     },
78554
78555     // private
78556     onMouseEnter: function(e) {
78557         if (this.el.parent().getRegion().contains(e.getPoint())) {
78558             this.fireEvent('mouseenter', e);
78559         }
78560     },
78561
78562     // private
78563     onMouseLeave: function(e) {
78564         if (!this.el.parent().getRegion().contains(e.getPoint())) {
78565             this.fireEvent('mouseleave', e);
78566         }
78567     },
78568     // @private - Normalize a delegated single event from the main container to each sprite and sprite group
78569     processEvent: function(name, e) {
78570         var target = e.getTarget(),
78571             surface = this.surface,
78572             sprite;
78573
78574         this.fireEvent(name, e);
78575         // We wrap text types in a tspan, sprite is the parent.
78576         if (target.nodeName == "tspan" && target.parentNode) {
78577             target = target.parentNode;
78578         }
78579         sprite = this.items.get(target.id);
78580         if (sprite) {
78581             sprite.fireEvent(name, sprite, e);
78582         }
78583     },
78584
78585     /* @private - Wrap SVG text inside a tspan to allow for line wrapping.  In addition this normallizes
78586      * the baseline for text the vertical middle of the text to be the same as VML.
78587      */
78588     tuneText: function (sprite, attrs) {
78589         var el = sprite.el.dom,
78590             tspans = [],
78591             height, tspan, text, i, ln, texts, factor;
78592
78593         if (attrs.hasOwnProperty("text")) {
78594            tspans = this.setText(sprite, attrs.text);
78595         }
78596         // Normalize baseline via a DY shift of first tspan. Shift other rows by height * line height (1.2)
78597         if (tspans.length) {
78598             height = this.getBBoxText(sprite).height;
78599             for (i = 0, ln = tspans.length; i < ln; i++) {
78600                 // The text baseline for FireFox 3.0 and 3.5 is different than other SVG implementations
78601                 // so we are going to normalize that here
78602                 factor = (Ext.isFF3_0 || Ext.isFF3_5) ? 2 : 4;
78603                 tspans[i].setAttribute("dy", i ? height * 1.2 : height / factor);
78604             }
78605             sprite.dirty = true;
78606         }
78607     },
78608
78609     setText: function(sprite, textString) {
78610          var me = this,
78611              el = sprite.el.dom,
78612              x = el.getAttribute("x"),
78613              tspans = [],
78614              height, tspan, text, i, ln, texts;
78615         
78616         while (el.firstChild) {
78617             el.removeChild(el.firstChild);
78618         }
78619         // Wrap each row into tspan to emulate rows
78620         texts = String(textString).split("\n");
78621         for (i = 0, ln = texts.length; i < ln; i++) {
78622             text = texts[i];
78623             if (text) {
78624                 tspan = me.createSvgElement("tspan");
78625                 tspan.appendChild(document.createTextNode(Ext.htmlDecode(text)));
78626                 tspan.setAttribute("x", x);
78627                 el.appendChild(tspan);
78628                 tspans[i] = tspan;
78629             }
78630         }
78631         return tspans;
78632     },
78633
78634     renderAll: function() {
78635         this.items.each(this.renderItem, this);
78636     },
78637
78638     renderItem: function (sprite) {
78639         if (!this.el) {
78640             return;
78641         }
78642         if (!sprite.el) {
78643             this.createSpriteElement(sprite);
78644         }
78645         if (sprite.zIndexDirty) {
78646             this.applyZIndex(sprite);
78647         }
78648         if (sprite.dirty) {
78649             this.applyAttrs(sprite);
78650             this.applyTransformations(sprite);
78651         }
78652     },
78653
78654     redraw: function(sprite) {
78655         sprite.dirty = sprite.zIndexDirty = true;
78656         this.renderItem(sprite);
78657     },
78658
78659     applyAttrs: function (sprite) {
78660         var me = this,
78661             el = sprite.el,
78662             group = sprite.group,
78663             sattr = sprite.attr,
78664             groups, i, ln, attrs, font, key, style, name, rect;
78665
78666         if (group) {
78667             groups = [].concat(group);
78668             ln = groups.length;
78669             for (i = 0; i < ln; i++) {
78670                 group = groups[i];
78671                 me.getGroup(group).add(sprite);
78672             }
78673             delete sprite.group;
78674         }
78675         attrs = me.scrubAttrs(sprite) || {};
78676
78677         // if (sprite.dirtyPath) {
78678         sprite.bbox.plain = 0;
78679         sprite.bbox.transform = 0;
78680             if (sprite.type == "circle" || sprite.type == "ellipse") {
78681                 attrs.cx = attrs.cx || attrs.x;
78682                 attrs.cy = attrs.cy || attrs.y;
78683             }
78684             else if (sprite.type == "rect") {
78685                 attrs.rx = attrs.ry = attrs.r;
78686             }
78687             else if (sprite.type == "path" && attrs.d) {
78688                 attrs.d = Ext.draw.Draw.pathToAbsolute(attrs.d);
78689             }
78690             sprite.dirtyPath = false;
78691         // }
78692
78693         if (attrs['clip-rect']) {
78694             me.setClip(sprite, attrs);
78695             delete attrs['clip-rect'];
78696         }
78697         if (sprite.type == 'text' && attrs.font && sprite.dirtyFont) {
78698             el.set({ style: "font: " + attrs.font});
78699             sprite.dirtyFont = false;
78700         }
78701         if (sprite.type == "image") {
78702             el.dom.setAttributeNS(me.xlink, "href", attrs.src);
78703         }
78704         Ext.applyIf(attrs, me.minDefaults[sprite.type]);
78705
78706         if (sprite.dirtyHidden) {
78707             (sattr.hidden) ? me.hidePrim(sprite) : me.showPrim(sprite);
78708             sprite.dirtyHidden = false;
78709         }
78710         for (key in attrs) {
78711             if (attrs.hasOwnProperty(key) && attrs[key] != null) {
78712                 el.dom.setAttribute(key, String(attrs[key]));
78713             }
78714         }
78715         if (sprite.type == 'text') {
78716             me.tuneText(sprite, attrs);
78717         }
78718
78719         //set styles
78720         style = sattr.style;
78721         if (style) {
78722             el.setStyle(style);
78723         }
78724
78725         sprite.dirty = false;
78726
78727         if (Ext.isSafari3) {
78728             // Refreshing the view to fix bug EXTJSIV-1: rendering issue in old Safari 3
78729             me.webkitRect.show();
78730             setTimeout(function () {
78731                 me.webkitRect.hide();
78732             });
78733         }
78734     },
78735
78736     setClip: function(sprite, params) {
78737         var me = this,
78738             rect = params["clip-rect"],
78739             clipEl, clipPath;
78740         if (rect) {
78741             if (sprite.clip) {
78742                 sprite.clip.parentNode.parentNode.removeChild(sprite.clip.parentNode);
78743             }
78744             clipEl = me.createSvgElement('clipPath');
78745             clipPath = me.createSvgElement('rect');
78746             clipEl.id = Ext.id(null, 'ext-clip-');
78747             clipPath.setAttribute("x", rect.x);
78748             clipPath.setAttribute("y", rect.y);
78749             clipPath.setAttribute("width", rect.width);
78750             clipPath.setAttribute("height", rect.height);
78751             clipEl.appendChild(clipPath);
78752             me.getDefs().appendChild(clipEl);
78753             sprite.el.dom.setAttribute("clip-path", "url(#" + clipEl.id + ")");
78754             sprite.clip = clipPath;
78755         }
78756         // if (!attrs[key]) {
78757         //     var clip = Ext.getDoc().dom.getElementById(sprite.el.getAttribute("clip-path").replace(/(^url\(#|\)$)/g, ""));
78758         //     clip && clip.parentNode.removeChild(clip);
78759         //     sprite.el.setAttribute("clip-path", "");
78760         //     delete attrss.clip;
78761         // }
78762     },
78763
78764     /**
78765      * Insert or move a given sprite's element to the correct place in the DOM list for its zIndex
78766      * @param {Ext.draw.Sprite} sprite
78767      */
78768     applyZIndex: function(sprite) {
78769         var idx = this.normalizeSpriteCollection(sprite),
78770             el = sprite.el,
78771             prevEl;
78772         if (this.el.dom.childNodes[idx + 2] !== el.dom) { //shift by 2 to account for defs and bg rect 
78773             if (idx > 0) {
78774                 // Find the first previous sprite which has its DOM element created already
78775                 do {
78776                     prevEl = this.items.getAt(--idx).el;
78777                 } while (!prevEl && idx > 0);
78778             }
78779             el.insertAfter(prevEl || this.bgRect);
78780         }
78781         sprite.zIndexDirty = false;
78782     },
78783
78784     createItem: function (config) {
78785         var sprite = Ext.create('Ext.draw.Sprite', config);
78786         sprite.surface = this;
78787         return sprite;
78788     },
78789
78790     addGradient: function(gradient) {
78791         gradient = Ext.draw.Draw.parseGradient(gradient);
78792         var ln = gradient.stops.length,
78793             vector = gradient.vector,
78794             gradientEl,
78795             stop,
78796             stopEl,
78797             i;
78798         if (gradient.type == "linear") {
78799             gradientEl = this.createSvgElement("linearGradient");
78800             gradientEl.setAttribute("x1", vector[0]);
78801             gradientEl.setAttribute("y1", vector[1]);
78802             gradientEl.setAttribute("x2", vector[2]);
78803             gradientEl.setAttribute("y2", vector[3]);
78804         }
78805         else {
78806             gradientEl = this.createSvgElement("radialGradient");
78807             gradientEl.setAttribute("cx", gradient.centerX);
78808             gradientEl.setAttribute("cy", gradient.centerY);
78809             gradientEl.setAttribute("r", gradient.radius);
78810             if (Ext.isNumber(gradient.focalX) && Ext.isNumber(gradient.focalY)) {
78811                 gradientEl.setAttribute("fx", gradient.focalX);
78812                 gradientEl.setAttribute("fy", gradient.focalY);
78813             }
78814         }    
78815         gradientEl.id = gradient.id;
78816         this.getDefs().appendChild(gradientEl);
78817
78818         for (i = 0; i < ln; i++) {
78819             stop = gradient.stops[i];
78820             stopEl = this.createSvgElement("stop");
78821             stopEl.setAttribute("offset", stop.offset + "%");
78822             stopEl.setAttribute("stop-color", stop.color);
78823             stopEl.setAttribute("stop-opacity",stop.opacity);
78824             gradientEl.appendChild(stopEl);
78825         }
78826     },
78827
78828     /**
78829      * Checks if the specified CSS class exists on this element's DOM node.
78830      * @param {String} className The CSS class to check for
78831      * @return {Boolean} True if the class exists, else false
78832      */
78833     hasCls: function(sprite, className) {
78834         return className && (' ' + (sprite.el.dom.getAttribute('class') || '') + ' ').indexOf(' ' + className + ' ') != -1;
78835     },
78836
78837     addCls: function(sprite, className) {
78838         var el = sprite.el,
78839             i,
78840             len,
78841             v,
78842             cls = [],
78843             curCls =  el.getAttribute('class') || '';
78844         // Separate case is for speed
78845         if (!Ext.isArray(className)) {
78846             if (typeof className == 'string' && !this.hasCls(sprite, className)) {
78847                 el.set({ 'class': curCls + ' ' + className });
78848             }
78849         }
78850         else {
78851             for (i = 0, len = className.length; i < len; i++) {
78852                 v = className[i];
78853                 if (typeof v == 'string' && (' ' + curCls + ' ').indexOf(' ' + v + ' ') == -1) {
78854                     cls.push(v);
78855                 }
78856             }
78857             if (cls.length) {
78858                 el.set({ 'class': ' ' + cls.join(' ') });
78859             }
78860         }
78861     },
78862
78863     removeCls: function(sprite, className) {
78864         var me = this,
78865             el = sprite.el,
78866             curCls =  el.getAttribute('class') || '',
78867             i, idx, len, cls, elClasses;
78868         if (!Ext.isArray(className)){
78869             className = [className];
78870         }
78871         if (curCls) {
78872             elClasses = curCls.replace(me.trimRe, ' ').split(me.spacesRe);
78873             for (i = 0, len = className.length; i < len; i++) {
78874                 cls = className[i];
78875                 if (typeof cls == 'string') {
78876                     cls = cls.replace(me.trimRe, '');
78877                     idx = Ext.Array.indexOf(elClasses, cls);
78878                     if (idx != -1) {
78879                         elClasses.splice(idx, 1);
78880                     }
78881                 }
78882             }
78883             el.set({ 'class': elClasses.join(' ') });
78884         }
78885     },
78886
78887     destroy: function() {
78888         var me = this;
78889         
78890         me.callParent();
78891         if (me.el) {
78892             me.el.remove();
78893         }
78894         delete me.el;
78895     }
78896 });
78897 /**
78898  * @class Ext.draw.engine.Vml
78899  * @extends Ext.draw.Surface
78900  * Provides specific methods to draw with VML.
78901  */
78902
78903 Ext.define('Ext.draw.engine.Vml', {
78904
78905     /* Begin Definitions */
78906
78907     extend: 'Ext.draw.Surface',
78908
78909     requires: ['Ext.draw.Draw', 'Ext.draw.Color', 'Ext.draw.Sprite', 'Ext.draw.Matrix', 'Ext.core.Element'],
78910
78911     /* End Definitions */
78912
78913     engine: 'Vml',
78914
78915     map: {M: "m", L: "l", C: "c", Z: "x", m: "t", l: "r", c: "v", z: "x"},
78916     bitesRe: /([clmz]),?([^clmz]*)/gi,
78917     valRe: /-?[^,\s-]+/g,
78918     fillUrlRe: /^url\(\s*['"]?([^\)]+?)['"]?\s*\)$/i,
78919     pathlike: /^(path|rect)$/,
78920     NonVmlPathRe: /[ahqstv]/ig, // Non-VML Pathing ops
78921     partialPathRe: /[clmz]/g,
78922     fontFamilyRe: /^['"]+|['"]+$/g,
78923     baseVmlCls: Ext.baseCSSPrefix + 'vml-base',
78924     vmlGroupCls: Ext.baseCSSPrefix + 'vml-group',
78925     spriteCls: Ext.baseCSSPrefix + 'vml-sprite',
78926     measureSpanCls: Ext.baseCSSPrefix + 'vml-measure-span',
78927     zoom: 21600,
78928     coordsize: 1000,
78929     coordorigin: '0 0',
78930
78931     // @private
78932     // Convert an SVG standard path into a VML path
78933     path2vml: function (path) {
78934         var me = this,
78935             nonVML =  me.NonVmlPathRe,
78936             map = me.map,
78937             val = me.valRe,
78938             zoom = me.zoom,
78939             bites = me.bitesRe,
78940             command = Ext.Function.bind(Ext.draw.Draw.pathToAbsolute, Ext.draw.Draw),
78941             res, pa, p, r, i, ii, j, jj;
78942         if (String(path).match(nonVML)) {
78943             command = Ext.Function.bind(Ext.draw.Draw.path2curve, Ext.draw.Draw);
78944         } else if (!String(path).match(me.partialPathRe)) {
78945             res = String(path).replace(bites, function (all, command, args) {
78946                 var vals = [],
78947                     isMove = command.toLowerCase() == "m",
78948                     res = map[command];
78949                 args.replace(val, function (value) {
78950                     if (isMove && vals[length] == 2) {
78951                         res += vals + map[command == "m" ? "l" : "L"];
78952                         vals = [];
78953                     }
78954                     vals.push(Math.round(value * zoom));
78955                 });
78956                 return res + vals;
78957             });
78958             return res;
78959         }
78960         pa = command(path);
78961         res = [];
78962         for (i = 0, ii = pa.length; i < ii; i++) {
78963             p = pa[i];
78964             r = pa[i][0].toLowerCase();
78965             if (r == "z") {
78966                 r = "x";
78967             }
78968             for (j = 1, jj = p.length; j < jj; j++) {
78969                 r += Math.round(p[j] * me.zoom) + (j != jj - 1 ? "," : "");
78970             }
78971             res.push(r);
78972         }
78973         return res.join(" ");
78974     },
78975
78976     // @private - set of attributes which need to be translated from the sprite API to the native browser API
78977     translateAttrs: {
78978         radius: "r",
78979         radiusX: "rx",
78980         radiusY: "ry",
78981         lineWidth: "stroke-width",
78982         fillOpacity: "fill-opacity",
78983         strokeOpacity: "stroke-opacity",
78984         strokeLinejoin: "stroke-linejoin"
78985     },
78986
78987     // @private - Minimun set of defaults for different types of sprites.
78988     minDefaults: {
78989         circle: {
78990             fill: "none",
78991             stroke: null,
78992             "stroke-width": null,
78993             opacity: null,
78994             "fill-opacity": null,
78995             "stroke-opacity": null
78996         },
78997         ellipse: {
78998             cx: 0,
78999             cy: 0,
79000             rx: 0,
79001             ry: 0,
79002             fill: "none",
79003             stroke: null,
79004             "stroke-width": null,
79005             opacity: null,
79006             "fill-opacity": null,
79007             "stroke-opacity": null
79008         },
79009         rect: {
79010             x: 0,
79011             y: 0,
79012             width: 0,
79013             height: 0,
79014             rx: 0,
79015             ry: 0,
79016             fill: "none",
79017             stroke: null,
79018             "stroke-width": null,
79019             opacity: null,
79020             "fill-opacity": null,
79021             "stroke-opacity": null
79022         },
79023         text: {
79024             x: 0,
79025             y: 0,
79026             "text-anchor": "start",
79027             font: '10px "Arial"',
79028             fill: "#000",
79029             stroke: null,
79030             "stroke-width": null,
79031             opacity: null,
79032             "fill-opacity": null,
79033             "stroke-opacity": null
79034         },
79035         path: {
79036             d: "M0,0",
79037             fill: "none",
79038             stroke: null,
79039             "stroke-width": null,
79040             opacity: null,
79041             "fill-opacity": null,
79042             "stroke-opacity": null
79043         },
79044         image: {
79045             x: 0,
79046             y: 0,
79047             width: 0,
79048             height: 0,
79049             preserveAspectRatio: "none",
79050             opacity: null
79051         }
79052     },
79053
79054     // private
79055     onMouseEnter: function(e) {
79056         this.fireEvent("mouseenter", e);
79057     },
79058
79059     // private
79060     onMouseLeave: function(e) {
79061         this.fireEvent("mouseleave", e);
79062     },
79063
79064     // @private - Normalize a delegated single event from the main container to each sprite and sprite group
79065     processEvent: function(name, e) {
79066         var target = e.getTarget(),
79067             surface = this.surface,
79068             sprite;
79069         this.fireEvent(name, e);
79070         sprite = this.items.get(target.id);
79071         if (sprite) {
79072             sprite.fireEvent(name, sprite, e);
79073         }
79074     },
79075
79076     // Create the VML element/elements and append them to the DOM
79077     createSpriteElement: function(sprite) {
79078         var me = this,
79079             attr = sprite.attr,
79080             type = sprite.type,
79081             zoom = me.zoom,
79082             vml = sprite.vml || (sprite.vml = {}),
79083             round = Math.round,
79084             el = (type === 'image') ? me.createNode('image') : me.createNode('shape'),
79085             path, skew, textPath;
79086
79087         el.coordsize = zoom + ' ' + zoom;
79088         el.coordorigin = attr.coordorigin || "0 0";
79089         Ext.get(el).addCls(me.spriteCls);
79090         if (type == "text") {
79091             vml.path = path = me.createNode("path");
79092             path.textpathok = true;
79093             vml.textpath = textPath = me.createNode("textpath");
79094             textPath.on = true;
79095             el.appendChild(textPath);
79096             el.appendChild(path);
79097         }
79098         el.id = sprite.id;
79099         sprite.el = Ext.get(el);
79100         me.el.appendChild(el);
79101         if (type !== 'image') {
79102             skew = me.createNode("skew");
79103             skew.on = true;
79104             el.appendChild(skew);
79105             sprite.skew = skew;
79106         }
79107         sprite.matrix = Ext.create('Ext.draw.Matrix');
79108         sprite.bbox = {
79109             plain: null,
79110             transform: null
79111         };
79112         sprite.fireEvent("render", sprite);
79113         return sprite.el;
79114     },
79115
79116     // @private - Get bounding box for the sprite.  The Sprite itself has the public method.
79117     getBBox: function (sprite, isWithoutTransform) {
79118         var realPath = this["getPath" + sprite.type](sprite);
79119         if (isWithoutTransform) {
79120             sprite.bbox.plain = sprite.bbox.plain || Ext.draw.Draw.pathDimensions(realPath);
79121             return sprite.bbox.plain;
79122         }
79123         sprite.bbox.transform = sprite.bbox.transform || Ext.draw.Draw.pathDimensions(Ext.draw.Draw.mapPath(realPath, sprite.matrix));
79124         return sprite.bbox.transform;
79125     },
79126
79127     getBBoxText: function (sprite) {
79128         var vml = sprite.vml;
79129         return {
79130             x: vml.X + (vml.bbx || 0) - vml.W / 2,
79131             y: vml.Y - vml.H / 2,
79132             width: vml.W,
79133             height: vml.H
79134         };
79135     },
79136
79137     applyAttrs: function (sprite) {
79138         var me = this,
79139             vml = sprite.vml,
79140             group = sprite.group,
79141             spriteAttr = sprite.attr,
79142             el = sprite.el,
79143             dom = el.dom,
79144             style, name, groups, i, ln, scrubbedAttrs, font, key, bbox;
79145
79146         if (group) {
79147             groups = [].concat(group);
79148             ln = groups.length;
79149             for (i = 0; i < ln; i++) {
79150                 group = groups[i];
79151                 me.getGroup(group).add(sprite);
79152             }
79153             delete sprite.group;
79154         }
79155         scrubbedAttrs = me.scrubAttrs(sprite) || {};
79156
79157         if (sprite.zIndexDirty) {
79158             me.setZIndex(sprite);
79159         }
79160
79161         // Apply minimum default attributes
79162         Ext.applyIf(scrubbedAttrs, me.minDefaults[sprite.type]);
79163
79164         if (sprite.type == 'image') {
79165             Ext.apply(sprite.attr, {
79166                 x: scrubbedAttrs.x,
79167                 y: scrubbedAttrs.y,
79168                 width: scrubbedAttrs.width,
79169                 height: scrubbedAttrs.height
79170             });
79171             bbox = sprite.getBBox();
79172             el.setStyle({
79173                 width: bbox.width + 'px',
79174                 height: bbox.height + 'px'
79175             });
79176             dom.src = scrubbedAttrs.src;
79177         }
79178
79179         if (dom.href) {
79180             dom.href = scrubbedAttrs.href;
79181         }
79182         if (dom.title) {
79183             dom.title = scrubbedAttrs.title;
79184         }
79185         if (dom.target) {
79186             dom.target = scrubbedAttrs.target;
79187         }
79188         if (dom.cursor) {
79189             dom.cursor = scrubbedAttrs.cursor;
79190         }
79191
79192         // Change visibility
79193         if (sprite.dirtyHidden) {
79194             (scrubbedAttrs.hidden) ? me.hidePrim(sprite) : me.showPrim(sprite);
79195             sprite.dirtyHidden = false;
79196         }
79197
79198         // Update path
79199         if (sprite.dirtyPath) {
79200             if (sprite.type == "circle" || sprite.type == "ellipse") {
79201                 var cx = scrubbedAttrs.x,
79202                     cy = scrubbedAttrs.y,
79203                     rx = scrubbedAttrs.rx || scrubbedAttrs.r || 0,
79204                     ry = scrubbedAttrs.ry || scrubbedAttrs.r || 0;
79205                 dom.path = Ext.String.format("ar{0},{1},{2},{3},{4},{1},{4},{1}",
79206                             Math.round((cx - rx) * me.zoom),
79207                             Math.round((cy - ry) * me.zoom),
79208                             Math.round((cx + rx) * me.zoom),
79209                             Math.round((cy + ry) * me.zoom),
79210                             Math.round(cx * me.zoom));
79211                 sprite.dirtyPath = false;
79212             }
79213             else if (sprite.type !== "text" && sprite.type !== 'image') {
79214                 sprite.attr.path = scrubbedAttrs.path = me.setPaths(sprite, scrubbedAttrs) || scrubbedAttrs.path;
79215                 dom.path = me.path2vml(scrubbedAttrs.path);
79216                 sprite.dirtyPath = false;
79217             }
79218         }
79219
79220         // Apply clipping
79221         if ("clip-rect" in scrubbedAttrs) {
79222             me.setClip(sprite, scrubbedAttrs);
79223         }
79224
79225         // Handle text (special handling required)
79226         if (sprite.type == "text") {
79227             me.setTextAttributes(sprite, scrubbedAttrs);
79228         }
79229
79230         // Handle fill and opacity
79231         if (scrubbedAttrs.opacity  || scrubbedAttrs['stroke-opacity'] || scrubbedAttrs.fill) {
79232             me.setFill(sprite, scrubbedAttrs);
79233         }
79234
79235         // Handle stroke (all fills require a stroke element)
79236         if (scrubbedAttrs.stroke || scrubbedAttrs['stroke-opacity'] || scrubbedAttrs.fill) {
79237             me.setStroke(sprite, scrubbedAttrs);
79238         }
79239         
79240         //set styles
79241         style = spriteAttr.style;
79242         if (style) {
79243             el.setStyle(style);
79244         }
79245
79246         sprite.dirty = false;
79247     },
79248
79249     setZIndex: function(sprite) {
79250         if (sprite.el) {
79251             if (sprite.attr.zIndex != undefined) {
79252                 sprite.el.setStyle('zIndex', sprite.attr.zIndex);
79253             }
79254             sprite.zIndexDirty = false;
79255         }
79256     },
79257
79258     // Normalize all virtualized types into paths.
79259     setPaths: function(sprite, params) {
79260         var spriteAttr = sprite.attr;
79261         // Clear bbox cache
79262         sprite.bbox.plain = null;
79263         sprite.bbox.transform = null;
79264         if (sprite.type == 'circle') {
79265             spriteAttr.rx = spriteAttr.ry = params.r;
79266             return Ext.draw.Draw.ellipsePath(sprite);
79267         }
79268         else if (sprite.type == 'ellipse') {
79269             spriteAttr.rx = params.rx;
79270             spriteAttr.ry = params.ry;
79271             return Ext.draw.Draw.ellipsePath(sprite);
79272         }
79273         else if (sprite.type == 'rect') {
79274             spriteAttr.rx = spriteAttr.ry = params.r;
79275             return Ext.draw.Draw.rectPath(sprite);
79276         }
79277         else if (sprite.type == 'path' && spriteAttr.path) {
79278             return Ext.draw.Draw.pathToAbsolute(spriteAttr.path);
79279         }
79280         return false;
79281     },
79282
79283     setFill: function(sprite, params) {
79284         var me = this,
79285             el = sprite.el.dom,
79286             fillEl = el.fill,
79287             newfill = false,
79288             opacity, gradient, fillUrl, rotation, angle;
79289
79290         if (!fillEl) {
79291             // NOT an expando (but it sure looks like one)...
79292             fillEl = el.fill = me.createNode("fill");
79293             newfill = true;
79294         }
79295         if (Ext.isArray(params.fill)) {
79296             params.fill = params.fill[0];
79297         }
79298         if (params.fill == "none") {
79299             fillEl.on = false;
79300         }
79301         else {
79302             if (typeof params.opacity == "number") {
79303                 fillEl.opacity = params.opacity;
79304             }
79305             if (typeof params["fill-opacity"] == "number") {
79306                 fillEl.opacity = params["fill-opacity"];
79307             }
79308             fillEl.on = true;
79309             if (typeof params.fill == "string") {
79310                 fillUrl = params.fill.match(me.fillUrlRe);
79311                 if (fillUrl) {
79312                     fillUrl = fillUrl[1];
79313                     // If the URL matches one of the registered gradients, render that gradient
79314                     if (fillUrl.charAt(0) == "#") {
79315                         gradient = me.gradientsColl.getByKey(fillUrl.substring(1));
79316                     }
79317                     if (gradient) {
79318                         // VML angle is offset and inverted from standard, and must be adjusted to match rotation transform
79319                         rotation = params.rotation;
79320                         angle = -(gradient.angle + 270 + (rotation ? rotation.degrees : 0)) % 360;
79321                         // IE will flip the angle at 0 degrees...
79322                         if (angle === 0) {
79323                             angle = 180;
79324                         }
79325                         fillEl.angle = angle;
79326                         fillEl.type = "gradient";
79327                         fillEl.method = "sigma";
79328                         fillEl.colors.value = gradient.colors;
79329                     }
79330                     // Otherwise treat it as an image
79331                     else {
79332                         fillEl.src = fillUrl;
79333                         fillEl.type = "tile";
79334                     }
79335                 }
79336                 else {
79337                     fillEl.color = Ext.draw.Color.toHex(params.fill);
79338                     fillEl.src = "";
79339                     fillEl.type = "solid";
79340                 }
79341             }
79342         }
79343         if (newfill) {
79344             el.appendChild(fillEl);
79345         }
79346     },
79347
79348     setStroke: function(sprite, params) {
79349         var me = this,
79350             el = sprite.el.dom,
79351             strokeEl = sprite.strokeEl,
79352             newStroke = false,
79353             width, opacity;
79354
79355         if (!strokeEl) {
79356             strokeEl = sprite.strokeEl = me.createNode("stroke");
79357             newStroke = true;
79358         }
79359         if (Ext.isArray(params.stroke)) {
79360             params.stroke = params.stroke[0];
79361         }
79362         if (!params.stroke || params.stroke == "none" || params.stroke == 0 || params["stroke-width"] == 0) {
79363             strokeEl.on = false;
79364         }
79365         else {
79366             strokeEl.on = true;
79367             if (params.stroke && !params.stroke.match(me.fillUrlRe)) {
79368                 // VML does NOT support a gradient stroke :(
79369                 strokeEl.color = Ext.draw.Color.toHex(params.stroke);
79370             }
79371             strokeEl.joinstyle = params["stroke-linejoin"];
79372             strokeEl.endcap = params["stroke-linecap"] || "round";
79373             strokeEl.miterlimit = params["stroke-miterlimit"] || 8;
79374             width = parseFloat(params["stroke-width"] || 1) * 0.75;
79375             opacity = params["stroke-opacity"] || 1;
79376             // VML Does not support stroke widths under 1, so we're going to fiddle with stroke-opacity instead.
79377             if (Ext.isNumber(width) && width < 1) {
79378                 strokeEl.weight = 1;
79379                 strokeEl.opacity = opacity * width;
79380             }
79381             else {
79382                 strokeEl.weight = width;
79383                 strokeEl.opacity = opacity;
79384             }
79385         }
79386         if (newStroke) {
79387             el.appendChild(strokeEl);
79388         }
79389     },
79390
79391     setClip: function(sprite, params) {
79392         var me = this,
79393             el = sprite.el,
79394             clipEl = sprite.clipEl,
79395             rect = String(params["clip-rect"]).split(me.separatorRe);
79396         if (!clipEl) {
79397             clipEl = sprite.clipEl = me.el.insertFirst(Ext.getDoc().dom.createElement("div"));
79398             clipEl.addCls(Ext.baseCSSPrefix + 'vml-sprite');
79399         }
79400         if (rect.length == 4) {
79401             rect[2] = +rect[2] + (+rect[0]);
79402             rect[3] = +rect[3] + (+rect[1]);
79403             clipEl.setStyle("clip", Ext.String.format("rect({1}px {2}px {3}px {0}px)", rect[0], rect[1], rect[2], rect[3]));
79404             clipEl.setSize(me.el.width, me.el.height);
79405         }
79406         else {
79407             clipEl.setStyle("clip", "");
79408         }
79409     },
79410
79411     setTextAttributes: function(sprite, params) {
79412         var me = this,
79413             vml = sprite.vml,
79414             textStyle = vml.textpath.style,
79415             spanCacheStyle = me.span.style,
79416             zoom = me.zoom,
79417             round = Math.round,
79418             fontObj = {
79419                 fontSize: "font-size",
79420                 fontWeight: "font-weight",
79421                 fontStyle: "font-style"
79422             },
79423             fontProp,
79424             paramProp;
79425         if (sprite.dirtyFont) {
79426             if (params.font) {
79427                 textStyle.font = spanCacheStyle.font = params.font;
79428             }
79429             if (params["font-family"]) {
79430                 textStyle.fontFamily = '"' + params["font-family"].split(",")[0].replace(me.fontFamilyRe, "") + '"';
79431                 spanCacheStyle.fontFamily = params["font-family"];
79432             }
79433
79434             for (fontProp in fontObj) {
79435                 paramProp = params[fontObj[fontProp]];
79436                 if (paramProp) {
79437                     textStyle[fontProp] = spanCacheStyle[fontProp] = paramProp;
79438                 }
79439             }
79440
79441             me.setText(sprite, params.text);
79442             
79443             if (vml.textpath.string) {
79444                 me.span.innerHTML = String(vml.textpath.string).replace(/</g, "&#60;").replace(/&/g, "&#38;").replace(/\n/g, "<br>");
79445             }
79446             vml.W = me.span.offsetWidth;
79447             vml.H = me.span.offsetHeight + 2; // TODO handle baseline differences and offset in VML Textpath
79448
79449             // text-anchor emulation
79450             if (params["text-anchor"] == "middle") {
79451                 textStyle["v-text-align"] = "center";
79452             }
79453             else if (params["text-anchor"] == "end") {
79454                 textStyle["v-text-align"] = "right";
79455                 vml.bbx = -Math.round(vml.W / 2);
79456             }
79457             else {
79458                 textStyle["v-text-align"] = "left";
79459                 vml.bbx = Math.round(vml.W / 2);
79460             }
79461         }
79462         vml.X = params.x;
79463         vml.Y = params.y;
79464         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);
79465         // Clear bbox cache
79466         sprite.bbox.plain = null;
79467         sprite.bbox.transform = null;
79468         sprite.dirtyFont = false;
79469     },
79470     
79471     setText: function(sprite, text) {
79472         sprite.vml.textpath.string = Ext.htmlDecode(text);
79473     },
79474
79475     hide: function() {
79476         this.el.hide();
79477     },
79478
79479     show: function() {
79480         this.el.show();
79481     },
79482
79483     hidePrim: function(sprite) {
79484         sprite.el.addCls(Ext.baseCSSPrefix + 'hide-visibility');
79485     },
79486
79487     showPrim: function(sprite) {
79488         sprite.el.removeCls(Ext.baseCSSPrefix + 'hide-visibility');
79489     },
79490
79491     setSize: function(width, height) {
79492         var me = this,
79493             viewBox = me.viewBox,
79494             scaleX, scaleY, items, i, len;
79495         width = width || me.width;
79496         height = height || me.height;
79497         me.width = width;
79498         me.height = height;
79499
79500         if (!me.el) {
79501             return;
79502         }
79503
79504         // Size outer div
79505         if (width != undefined) {
79506             me.el.setWidth(width);
79507         }
79508         if (height != undefined) {
79509             me.el.setHeight(height);
79510         }
79511
79512         // Handle viewBox sizing
79513         if (viewBox && (width || height)) {
79514             var viewBoxX = viewBox.x,
79515                 viewBoxY = viewBox.y,
79516                 viewBoxWidth = viewBox.width,
79517                 viewBoxHeight = viewBox.height,
79518                 relativeHeight = height / viewBoxHeight,
79519                 relativeWidth = width / viewBoxWidth,
79520                 size;
79521             if (viewBoxWidth * relativeHeight < width) {
79522                 viewBoxX -= (width - viewBoxWidth * relativeHeight) / 2 / relativeHeight;
79523             }
79524             if (viewBoxHeight * relativeWidth < height) {
79525                 viewBoxY -= (height - viewBoxHeight * relativeWidth) / 2 / relativeWidth;
79526             }
79527             size = 1 / Math.max(viewBoxWidth / width, viewBoxHeight / height);
79528             // Scale and translate group
79529             me.viewBoxShift = {
79530                 dx: -viewBoxX,
79531                 dy: -viewBoxY,
79532                 scale: size
79533             };
79534             items = me.items.items;
79535             for (i = 0, len = items.length; i < len; i++) {
79536                 me.transform(items[i]);
79537             }
79538         }
79539         this.callParent(arguments);
79540     },
79541
79542     setViewBox: function(x, y, width, height) {
79543         this.callParent(arguments);
79544         this.viewBox = {
79545             x: x,
79546             y: y,
79547             width: width,
79548             height: height
79549         };
79550     },
79551
79552     onAdd: function(item) {
79553         this.callParent(arguments);
79554         if (this.el) {
79555             this.renderItem(item);
79556         }
79557     },
79558
79559     onRemove: function(sprite) {
79560         if (sprite.el) {
79561             sprite.el.remove();
79562             delete sprite.el;
79563         }
79564         this.callParent(arguments);
79565     },
79566
79567     render: function (container) {
79568         var me = this,
79569             doc = Ext.getDoc().dom;
79570         // VML Node factory method (createNode)
79571         if (!me.createNode) {
79572             try {
79573                 if (!doc.namespaces.rvml) {
79574                     doc.namespaces.add("rvml", "urn:schemas-microsoft-com:vml");
79575                 }
79576                 me.createNode = function (tagName) {
79577                     return doc.createElement("<rvml:" + tagName + ' class="rvml">');
79578                 };
79579             } catch (e) {
79580                 me.createNode = function (tagName) {
79581                     return doc.createElement("<" + tagName + ' xmlns="urn:schemas-microsoft.com:vml" class="rvml">');
79582                 };
79583             }
79584         }
79585
79586         if (!me.el) {
79587             var el = doc.createElement("div");
79588             me.el = Ext.get(el);
79589             me.el.addCls(me.baseVmlCls);
79590
79591             // Measuring span (offscrren)
79592             me.span = doc.createElement("span");
79593             Ext.get(me.span).addCls(me.measureSpanCls);
79594             el.appendChild(me.span);
79595             me.el.setSize(me.width || 10, me.height || 10);
79596             container.appendChild(el);
79597             me.el.on({
79598                 scope: me,
79599                 mouseup: me.onMouseUp,
79600                 mousedown: me.onMouseDown,
79601                 mouseover: me.onMouseOver,
79602                 mouseout: me.onMouseOut,
79603                 mousemove: me.onMouseMove,
79604                 mouseenter: me.onMouseEnter,
79605                 mouseleave: me.onMouseLeave,
79606                 click: me.onClick
79607             });
79608         }
79609         me.renderAll();
79610     },
79611
79612     renderAll: function() {
79613         this.items.each(this.renderItem, this);
79614     },
79615
79616     redraw: function(sprite) {
79617         sprite.dirty = true;
79618         this.renderItem(sprite);
79619     },
79620
79621     renderItem: function (sprite) {
79622         // Does the surface element exist?
79623         if (!this.el) {
79624             return;
79625         }
79626
79627         // Create sprite element if necessary
79628         if (!sprite.el) {
79629             this.createSpriteElement(sprite);
79630         }
79631
79632         if (sprite.dirty) {
79633             this.applyAttrs(sprite);
79634             if (sprite.dirtyTransform) {
79635                 this.applyTransformations(sprite);
79636             }
79637         }
79638     },
79639
79640     rotationCompensation: function (deg, dx, dy) {
79641         var matrix = Ext.create('Ext.draw.Matrix');
79642         matrix.rotate(-deg, 0.5, 0.5);
79643         return {
79644             x: matrix.x(dx, dy),
79645             y: matrix.y(dx, dy)
79646         };
79647     },
79648
79649     transform: function(sprite) {
79650         var me = this,
79651             matrix = Ext.create('Ext.draw.Matrix'),
79652             transforms = sprite.transformations,
79653             transformsLength = transforms.length,
79654             i = 0,
79655             deltaDegrees = 0,
79656             deltaScaleX = 1,
79657             deltaScaleY = 1,
79658             flip = "",
79659             el = sprite.el,
79660             dom = el.dom,
79661             domStyle = dom.style,
79662             zoom = me.zoom,
79663             skew = sprite.skew,
79664             deltaX, deltaY, transform, type, compensate, y, fill, newAngle,zoomScaleX, zoomScaleY, newOrigin;
79665
79666         for (; i < transformsLength; i++) {
79667             transform = transforms[i];
79668             type = transform.type;
79669             if (type == "translate") {
79670                 matrix.translate(transform.x, transform.y);
79671             }
79672             else if (type == "rotate") {
79673                 matrix.rotate(transform.degrees, transform.x, transform.y);
79674                 deltaDegrees += transform.degrees;
79675             }
79676             else if (type == "scale") {
79677                 matrix.scale(transform.x, transform.y, transform.centerX, transform.centerY);
79678                 deltaScaleX *= transform.x;
79679                 deltaScaleY *= transform.y;
79680             }
79681         }
79682
79683         if (me.viewBoxShift) {
79684             matrix.scale(me.viewBoxShift.scale, me.viewBoxShift.scale, -1, -1);
79685             matrix.add(1, 0, 0, 1, me.viewBoxShift.dx, me.viewBoxShift.dy);
79686         }
79687
79688         sprite.matrix = matrix;
79689
79690
79691         // Hide element while we transform
79692
79693         if (sprite.type != "image" && skew) {
79694             // matrix transform via VML skew
79695             skew.matrix = matrix.toString();
79696             skew.offset = matrix.offset();
79697         }
79698         else {
79699             deltaX = matrix.matrix[0][2];
79700             deltaY = matrix.matrix[1][2];
79701             // Scale via coordsize property
79702             zoomScaleX = zoom / deltaScaleX;
79703             zoomScaleY = zoom / deltaScaleY;
79704
79705             dom.coordsize = Math.abs(zoomScaleX) + " " + Math.abs(zoomScaleY);
79706
79707             // Rotate via rotation property
79708             newAngle = deltaDegrees * (deltaScaleX * ((deltaScaleY < 0) ? -1 : 1));
79709             if (newAngle != domStyle.rotation && !(newAngle === 0 && !domStyle.rotation)) {
79710                 domStyle.rotation = newAngle;
79711             }
79712             if (deltaDegrees) {
79713                 // Compensate x/y position due to rotation
79714                 compensate = me.rotationCompensation(deltaDegrees, deltaX, deltaY);
79715                 deltaX = compensate.x;
79716                 deltaY = compensate.y;
79717             }
79718
79719             // Handle negative scaling via flipping
79720             if (deltaScaleX < 0) {
79721                 flip += "x";
79722             }
79723             if (deltaScaleY < 0) {
79724                 flip += " y";
79725                 y = -1;
79726             }
79727             if (flip != "" && !dom.style.flip) {
79728                 domStyle.flip = flip;
79729             }
79730
79731             // Translate via coordorigin property
79732             newOrigin = (deltaX * -zoomScaleX) + " " + (deltaY * -zoomScaleY);
79733             if (newOrigin != dom.coordorigin) {
79734                 dom.coordorigin = (deltaX * -zoomScaleX) + " " + (deltaY * -zoomScaleY);
79735             }
79736         }
79737     },
79738
79739     createItem: function (config) {
79740         return Ext.create('Ext.draw.Sprite', config);
79741     },
79742
79743     getRegion: function() {
79744         return this.el.getRegion();
79745     },
79746
79747     addCls: function(sprite, className) {
79748         if (sprite && sprite.el) {
79749             sprite.el.addCls(className);
79750         }
79751     },
79752
79753     removeCls: function(sprite, className) {
79754         if (sprite && sprite.el) {
79755             sprite.el.removeCls(className);
79756         }
79757     },
79758
79759     /**
79760      * Adds a definition to this Surface for a linear gradient. We convert the gradient definition
79761      * to its corresponding VML attributes and store it for later use by individual sprites.
79762      * @param {Object} gradient
79763      */
79764     addGradient: function(gradient) {
79765         var gradients = this.gradientsColl || (this.gradientsColl = Ext.create('Ext.util.MixedCollection')),
79766             colors = [],
79767             stops = Ext.create('Ext.util.MixedCollection');
79768
79769         // Build colors string
79770         stops.addAll(gradient.stops);
79771         stops.sortByKey("ASC", function(a, b) {
79772             a = parseInt(a, 10);
79773             b = parseInt(b, 10);
79774             return a > b ? 1 : (a < b ? -1 : 0);
79775         });
79776         stops.eachKey(function(k, v) {
79777             colors.push(k + "% " + v.color);
79778         });
79779
79780         gradients.add(gradient.id, {
79781             colors: colors.join(","),
79782             angle: gradient.angle
79783         });
79784     },
79785
79786     destroy: function() {
79787         var me = this;
79788         
79789         me.callParent(arguments);
79790         if (me.el) {
79791             me.el.remove();
79792         }
79793         delete me.el;
79794     }
79795 });
79796
79797 /**
79798  * @class Ext.fx.target.ElementCSS
79799  * @extends Ext.fx.target.Element
79800  * 
79801  * This class represents a animation target for an {@link Ext.core.Element} that supports CSS
79802  * based animation. In general this class will not be created directly, the {@link Ext.core.Element} 
79803  * will be passed to the animation and the appropriate target will be created.
79804  */
79805 Ext.define('Ext.fx.target.ElementCSS', {
79806
79807     /* Begin Definitions */
79808
79809     extend: 'Ext.fx.target.Element',
79810
79811     /* End Definitions */
79812
79813     setAttr: function(targetData, isFirstFrame) {
79814         var cssArr = {
79815                 attrs: [],
79816                 duration: [],
79817                 easing: []
79818             },
79819             ln = targetData.length,
79820             attributes,
79821             attrs,
79822             attr,
79823             easing,
79824             duration,
79825             o,
79826             i,
79827             j,
79828             ln2;
79829         for (i = 0; i < ln; i++) {
79830             attrs = targetData[i];
79831             duration = attrs.duration;
79832             easing = attrs.easing;
79833             attrs = attrs.attrs;
79834             for (attr in attrs) {
79835                 if (Ext.Array.indexOf(cssArr.attrs, attr) == -1) {
79836                     cssArr.attrs.push(attr.replace(/[A-Z]/g, function(v) {
79837                         return '-' + v.toLowerCase();
79838                     }));
79839                     cssArr.duration.push(duration + 'ms');
79840                     cssArr.easing.push(easing);
79841                 }
79842             }
79843         }
79844         attributes = cssArr.attrs.join(',');
79845         duration = cssArr.duration.join(',');
79846         easing = cssArr.easing.join(', ');
79847         for (i = 0; i < ln; i++) {
79848             attrs = targetData[i].attrs;
79849             for (attr in attrs) {
79850                 ln2 = attrs[attr].length;
79851                 for (j = 0; j < ln2; j++) {
79852                     o = attrs[attr][j];
79853                     o[0].setStyle(Ext.supports.CSS3Prefix + 'TransitionProperty', isFirstFrame ? '' : attributes);
79854                     o[0].setStyle(Ext.supports.CSS3Prefix + 'TransitionDuration', isFirstFrame ? '' : duration);
79855                     o[0].setStyle(Ext.supports.CSS3Prefix + 'TransitionTimingFunction', isFirstFrame ? '' : easing);
79856                     o[0].setStyle(attr, o[1]);
79857
79858                     // Must trigger reflow to make this get used as the start point for the transition that follows
79859                     if (isFirstFrame) {
79860                         o = o[0].dom.offsetWidth;
79861                     }
79862                     else {
79863                         // Remove transition properties when completed.
79864                         o[0].on(Ext.supports.CSS3TransitionEnd, function() {
79865                             this.setStyle(Ext.supports.CSS3Prefix + 'TransitionProperty', null);
79866                             this.setStyle(Ext.supports.CSS3Prefix + 'TransitionDuration', null);
79867                             this.setStyle(Ext.supports.CSS3Prefix + 'TransitionTimingFunction', null);
79868                         }, o[0], { single: true });
79869                     }
79870                 }
79871             }
79872         }
79873     }
79874 });
79875 /**
79876  * @class Ext.fx.target.CompositeElementCSS
79877  * @extends Ext.fx.target.CompositeElement
79878  * 
79879  * This class represents a animation target for a {@link Ext.CompositeElement}, where the
79880  * constituent elements support CSS based animation. It allows each {@link Ext.core.Element} in 
79881  * the group to be animated as a whole. In general this class will not be created directly, 
79882  * the {@link Ext.CompositeElement} will be passed to the animation and the appropriate target 
79883  * will be created.
79884  */
79885 Ext.define('Ext.fx.target.CompositeElementCSS', {
79886
79887     /* Begin Definitions */
79888
79889     extend: 'Ext.fx.target.CompositeElement',
79890
79891     requires: ['Ext.fx.target.ElementCSS'],
79892
79893     /* End Definitions */
79894     setAttr: function() {
79895         return Ext.fx.target.ElementCSS.prototype.setAttr.apply(this, arguments);
79896     }
79897 });
79898 /**
79899  * @class Ext.layout.container.AbstractFit
79900  * @extends Ext.layout.container.Container
79901  * <p>This is a base class for layouts that contain <b>a single item</b> that automatically expands to fill the layout's
79902  * container.  This class is intended to be extended or created via the <tt>layout:'fit'</tt> {@link Ext.container.Container#layout}
79903  * config, and should generally not need to be created directly via the new keyword.</p>
79904  * <p>FitLayout does not have any direct config options (other than inherited ones).  To fit a panel to a container
79905  * using FitLayout, simply set layout:'fit' on the container and add a single panel to it.  If the container has
79906  * multiple panels, only the first one will be displayed.  Example usage:</p>
79907  * <pre><code>
79908 var p = new Ext.panel.Panel({
79909     title: 'Fit Layout',
79910     layout:'fit',
79911     items: {
79912         title: 'Inner Panel',
79913         html: '&lt;p&gt;This is the inner panel content&lt;/p&gt;',
79914         border: false
79915     }
79916 });
79917 </code></pre>
79918  */
79919 Ext.define('Ext.layout.container.AbstractFit', {
79920
79921     /* Begin Definitions */
79922
79923     extend: 'Ext.layout.container.Container',
79924
79925     /* End Definitions */
79926
79927     itemCls: Ext.baseCSSPrefix + 'fit-item',
79928     targetCls: Ext.baseCSSPrefix + 'layout-fit',
79929     type: 'fit'
79930 });
79931 /**
79932  * @class Ext.layout.container.Fit
79933  * @extends Ext.layout.container.AbstractFit
79934  * <p>This is a base class for layouts that contain <b>a single item</b> that automatically expands to fill the layout's
79935  * container.  This class is intended to be extended or created via the <tt>layout:'fit'</tt> {@link Ext.container.Container#layout}
79936  * config, and should generally not need to be created directly via the new keyword.</p>
79937  * <p>FitLayout does not have any direct config options (other than inherited ones).  To fit a panel to a container
79938  * using FitLayout, simply set layout:'fit' on the container and add a single panel to it.  If the container has
79939  * multiple panels, only the first one will be displayed.  
79940  * {@img Ext.layout.container.Fit/Ext.layout.container.Fit.png Ext.layout.container.Fit container layout}
79941  * Example usage:</p>
79942  * <pre><code>
79943     Ext.create('Ext.panel.Panel', {
79944         title: 'Fit Layout',
79945         width: 300,
79946         height: 150,
79947         layout:'fit',
79948         items: {
79949             title: 'Inner Panel',
79950             html: '<p>This is the inner panel content</p>',
79951             bodyPadding: 20,
79952             border: false
79953         },
79954         renderTo: Ext.getBody()
79955     });  
79956 </code></pre>
79957  */
79958 Ext.define('Ext.layout.container.Fit', {
79959
79960     /* Begin Definitions */
79961
79962     extend: 'Ext.layout.container.AbstractFit',
79963     alias: 'layout.fit',
79964     alternateClassName: 'Ext.layout.FitLayout',
79965
79966     /* End Definitions */
79967    
79968     // @private
79969     onLayout : function() {
79970         var me = this;
79971         me.callParent();
79972
79973         if (me.owner.items.length) {
79974             me.setItemBox(me.owner.items.get(0), me.getLayoutTargetSize());
79975         }
79976     },
79977
79978     getTargetBox : function() {
79979         return this.getLayoutTargetSize();
79980     },
79981
79982     setItemBox : function(item, box) {
79983         var me = this;
79984         if (item && box.height > 0) {
79985             if (me.isManaged('width') === true) {
79986                box.width = undefined;
79987             }
79988             if (me.isManaged('height') === true) {
79989                box.height = undefined;
79990             }
79991             me.setItemSize(item, box.width, box.height);
79992         }
79993     }
79994 });
79995 /**
79996  * @class Ext.layout.container.AbstractCard
79997  * @extends Ext.layout.container.Fit
79998  * <p>This layout manages multiple child Components, each is fit to the Container, where only a single child Component
79999  * can be visible at any given time.  This layout style is most commonly used for wizards, tab implementations, etc.
80000  * This class is intended to be extended or created via the layout:'card' {@link Ext.container.Container#layout} config,
80001  * and should generally not need to be created directly via the new keyword.</p>
80002  * <p>The CardLayout's focal method is {@link #setActiveItem}.  Since only one panel is displayed at a time,
80003  * the only way to move from one Component to the next is by calling setActiveItem, passing the id or index of
80004  * the next panel to display.  The layout itself does not provide a user interface for handling this navigation,
80005  * so that functionality must be provided by the developer.</p>
80006  * <p>Containers that are configured with a card layout will have a method setActiveItem dynamically added to it.
80007  * <pre><code>
80008       var p = new Ext.panel.Panel({
80009           fullscreen: true,
80010           layout: 'card',
80011           items: [{
80012               html: 'Card 1'
80013           },{
80014               html: 'Card 2'
80015           }]
80016       });
80017       p.setActiveItem(1);
80018    </code></pre>
80019  * </p>
80020  */
80021
80022 Ext.define('Ext.layout.container.AbstractCard', {
80023
80024     /* Begin Definitions */
80025
80026     extend: 'Ext.layout.container.Fit',
80027
80028     /* End Definitions */
80029
80030     type: 'card',
80031
80032     sizeAllCards: false,
80033
80034     hideInactive: true,
80035
80036     /**
80037      * @cfg {Boolean} deferredRender
80038      * True to render each contained item at the time it becomes active, false to render all contained items
80039      * as soon as the layout is rendered (defaults to false).  If there is a significant amount of content or
80040      * a lot of heavy controls being rendered into panels that are not displayed by default, setting this to
80041      * true might improve performance.
80042      */
80043     deferredRender : false,
80044
80045     beforeLayout: function() {
80046         var me = this;
80047         me.activeItem = me.getActiveItem();
80048         if (me.activeItem && me.deferredRender) {
80049             me.renderItems([me.activeItem], me.getRenderTarget());
80050             return true;
80051         }
80052         else {
80053             return this.callParent(arguments);
80054         }
80055     },
80056
80057     onLayout: function() {
80058         var me = this,
80059             activeItem = me.activeItem,
80060             items = me.getVisibleItems(),
80061             ln = items.length,
80062             targetBox = me.getTargetBox(),
80063             i, item;
80064
80065         for (i = 0; i < ln; i++) {
80066             item = items[i];
80067             me.setItemBox(item, targetBox);
80068         }
80069
80070         if (!me.firstActivated && activeItem) {
80071             if (activeItem.fireEvent('beforeactivate', activeItem) !== false) {
80072                 activeItem.fireEvent('activate', activeItem);
80073             }
80074             me.firstActivated = true;
80075         }
80076     },
80077
80078     isValidParent : function(item, target, position) {
80079         // Note: Card layout does not care about order within the target because only one is ever visible.
80080         // We only care whether the item is a direct child of the target.
80081         var itemEl = item.el ? item.el.dom : Ext.getDom(item);
80082         return (itemEl && itemEl.parentNode === (target.dom || target)) || false;
80083     },
80084
80085     /**
80086      * Return the active (visible) component in the layout.
80087      * @returns {Ext.Component}
80088      */
80089     getActiveItem: function() {
80090         var me = this;
80091         if (!me.activeItem && me.owner) {
80092             me.activeItem = me.parseActiveItem(me.owner.activeItem);
80093         }
80094
80095         if (me.activeItem && me.owner.items.indexOf(me.activeItem) != -1) {
80096             return me.activeItem;
80097         }
80098
80099         return null;
80100     },
80101
80102     // @private
80103     parseActiveItem: function(item) {
80104         if (item && item.isComponent) {
80105             return item;
80106         }
80107         else if (typeof item == 'number' || item === undefined) {
80108             return this.getLayoutItems()[item || 0];
80109         }
80110         else {
80111             return this.owner.getComponent(item);
80112         }
80113     },
80114
80115     // @private
80116     configureItem: function(item, position) {
80117         this.callParent([item, position]);
80118         if (this.hideInactive && this.activeItem !== item) {
80119             item.hide();
80120         }
80121         else {
80122             item.show();
80123         }
80124     },
80125
80126     onRemove: function(component) {
80127         if (component === this.activeItem) {
80128             this.activeItem = null;
80129             if (this.owner.items.getCount() === 0) {
80130                 this.firstActivated = false;
80131             }
80132         }
80133     },
80134
80135     // @private
80136     getAnimation: function(newCard, owner) {
80137         var newAnim = (newCard || {}).cardSwitchAnimation;
80138         if (newAnim === false) {
80139             return false;
80140         }
80141         return newAnim || owner.cardSwitchAnimation;
80142     },
80143
80144     /**
80145      * Return the active (visible) component in the layout to the next card
80146      * @returns {Ext.Component}
80147      */
80148     getNext: function(wrap) {
80149         //NOTE: Removed the JSDoc for this function's arguments because it is not actually supported in 4.0. This 
80150         //should come back in 4.1
80151         
80152         var items = this.getLayoutItems(),
80153             index = Ext.Array.indexOf(items, this.activeItem);
80154         return items[index + 1] || (wrap ? items[0] : false);
80155     },
80156
80157     /**
80158      * Sets the active (visible) component in the layout to the next card
80159      */
80160     next: function(anim, wrap) {
80161         //NOTE: Removed the JSDoc for this function's arguments because it is not actually supported in 4.0. This 
80162         //should come back in 4.1
80163         
80164         return this.setActiveItem(this.getNext(wrap), anim);
80165     },
80166
80167     /**
80168      * Return the active (visible) component in the layout to the previous card
80169      * @returns {Ext.Component}
80170      */
80171     getPrev: function(wrap) {
80172         //NOTE: Removed the JSDoc for this function's arguments because it is not actually supported in 4.0. This 
80173         //should come back in 4.1
80174         
80175         var items = this.getLayoutItems(),
80176             index = Ext.Array.indexOf(items, this.activeItem);
80177         return items[index - 1] || (wrap ? items[items.length - 1] : false);
80178     },
80179
80180     /**
80181      * Sets the active (visible) component in the layout to the previous card
80182      */
80183     prev: function(anim, wrap) {
80184         //NOTE: Removed the JSDoc for this function's arguments because it is not actually supported in 4.0. This 
80185         //should come back in 4.1
80186         
80187         return this.setActiveItem(this.getPrev(wrap), anim);
80188     }
80189 });
80190
80191 /**
80192  * @class Ext.selection.Model
80193  * @extends Ext.util.Observable
80194  *
80195  * Tracks what records are currently selected in a databound widget.
80196  *
80197  * This is an abstract class and is not meant to be directly used.
80198  *
80199  * DataBound UI widgets such as GridPanel, TreePanel, and ListView
80200  * should subclass AbstractStoreSelectionModel and provide a way
80201  * to binding to the component.
80202  *
80203  * The abstract methods onSelectChange and onLastFocusChanged should
80204  * be implemented in these subclasses to update the UI widget.
80205  */
80206 Ext.define('Ext.selection.Model', {
80207     extend: 'Ext.util.Observable',
80208     alternateClassName: 'Ext.AbstractStoreSelectionModel',
80209     requires: ['Ext.data.StoreManager'],
80210     // lastSelected
80211
80212     /**
80213      * @cfg {String} mode
80214      * Modes of selection.
80215      * Valid values are SINGLE, SIMPLE, and MULTI. Defaults to 'SINGLE'
80216      */
80217     
80218     /**
80219      * @cfg {Boolean} allowDeselect
80220      * Allow users to deselect a record in a DataView, List or Grid. Only applicable when the SelectionModel's mode is 'SINGLE'. Defaults to false.
80221      */
80222     allowDeselect: false,
80223
80224     /**
80225      * @property selected
80226      * READ-ONLY A MixedCollection that maintains all of the currently selected
80227      * records.
80228      */
80229     selected: null,
80230     
80231     
80232     /**
80233      * Prune records when they are removed from the store from the selection.
80234      * This is a private flag. For an example of its usage, take a look at
80235      * Ext.selection.TreeModel.
80236      * @private
80237      */
80238     pruneRemoved: true,
80239
80240     constructor: function(cfg) {
80241         var me = this;
80242         
80243         cfg = cfg || {};
80244         Ext.apply(me, cfg);
80245         
80246         me.addEvents(
80247             /**
80248              * @event selectionchange
80249              * Fired after a selection change has occurred
80250              * @param {Ext.selection.Model} this
80251              * @param  {Array} selected The selected records
80252              */
80253              'selectionchange'
80254         );
80255
80256         me.modes = {
80257             SINGLE: true,
80258             SIMPLE: true,
80259             MULTI: true
80260         };
80261
80262         // sets this.selectionMode
80263         me.setSelectionMode(cfg.mode || me.mode);
80264
80265         // maintains the currently selected records.
80266         me.selected = Ext.create('Ext.util.MixedCollection');
80267         
80268         me.callParent(arguments);
80269     },
80270
80271     // binds the store to the selModel.
80272     bind : function(store, initial){
80273         var me = this;
80274         
80275         if(!initial && me.store){
80276             if(store !== me.store && me.store.autoDestroy){
80277                 me.store.destroy();
80278             }else{
80279                 me.store.un("add", me.onStoreAdd, me);
80280                 me.store.un("clear", me.onStoreClear, me);
80281                 me.store.un("remove", me.onStoreRemove, me);
80282                 me.store.un("update", me.onStoreUpdate, me);
80283             }
80284         }
80285         if(store){
80286             store = Ext.data.StoreManager.lookup(store);
80287             store.on({
80288                 add: me.onStoreAdd,
80289                 clear: me.onStoreClear,
80290                 remove: me.onStoreRemove,
80291                 update: me.onStoreUpdate,
80292                 scope: me
80293             });
80294         }
80295         me.store = store;
80296         if(store && !initial) {
80297             me.refresh();
80298         }
80299     },
80300
80301     selectAll: function(silent) {
80302         var selections = this.store.getRange(),
80303             i = 0,
80304             len = selections.length;
80305             
80306         for (; i < len; i++) {
80307             this.doSelect(selections[i], true, silent);
80308         }
80309     },
80310
80311     deselectAll: function() {
80312         var selections = this.getSelection(),
80313             i = 0,
80314             len = selections.length;
80315             
80316         for (; i < len; i++) {
80317             this.doDeselect(selections[i]);
80318         }
80319     },
80320
80321     // Provides differentiation of logic between MULTI, SIMPLE and SINGLE
80322     // selection modes. Requires that an event be passed so that we can know
80323     // if user held ctrl or shift.
80324     selectWithEvent: function(record, e) {
80325         var me = this;
80326         
80327         switch (me.selectionMode) {
80328             case 'MULTI':
80329                 if (e.ctrlKey && me.isSelected(record)) {
80330                     me.doDeselect(record, false);
80331                 } else if (e.shiftKey && me.lastFocused) {
80332                     me.selectRange(me.lastFocused, record, e.ctrlKey);
80333                 } else if (e.ctrlKey) {
80334                     me.doSelect(record, true, false);
80335                 } else if (me.isSelected(record) && !e.shiftKey && !e.ctrlKey && me.selected.getCount() > 1) {
80336                     me.doSelect(record, false, false);
80337                 } else {
80338                     me.doSelect(record, false);
80339                 }
80340                 break;
80341             case 'SIMPLE':
80342                 if (me.isSelected(record)) {
80343                     me.doDeselect(record);
80344                 } else {
80345                     me.doSelect(record, true);
80346                 }
80347                 break;
80348             case 'SINGLE':
80349                 // if allowDeselect is on and this record isSelected, deselect it
80350                 if (me.allowDeselect && me.isSelected(record)) {
80351                     me.doDeselect(record);
80352                 // select the record and do NOT maintain existing selections
80353                 } else {
80354                     me.doSelect(record, false);
80355                 }
80356                 break;
80357         }
80358     },
80359
80360     /**
80361      * Selects a range of rows if the selection model {@link #isLocked is not locked}.
80362      * All rows in between startRow and endRow are also selected.
80363      * @param {Ext.data.Model/Number} startRow The record or index of the first row in the range
80364      * @param {Ext.data.Model/Number} endRow The record or index of the last row in the range
80365      * @param {Boolean} keepExisting (optional) True to retain existing selections
80366      */
80367     selectRange : function(startRow, endRow, keepExisting, dir){
80368         var me = this,
80369             store = me.store,
80370             selectedCount = 0,
80371             i,
80372             tmp,
80373             dontDeselect,
80374             records = [];
80375         
80376         if (me.isLocked()){
80377             return;
80378         }
80379         
80380         if (!keepExisting) {
80381             me.clearSelections();
80382         }
80383         
80384         if (!Ext.isNumber(startRow)) {
80385             startRow = store.indexOf(startRow);
80386         } 
80387         if (!Ext.isNumber(endRow)) {
80388             endRow = store.indexOf(endRow);
80389         }
80390         
80391         // swap values
80392         if (startRow > endRow){
80393             tmp = endRow;
80394             endRow = startRow;
80395             startRow = tmp;
80396         }
80397
80398         for (i = startRow; i <= endRow; i++) {
80399             if (me.isSelected(store.getAt(i))) {
80400                 selectedCount++;
80401             }
80402         }
80403
80404         if (!dir) {
80405             dontDeselect = -1;
80406         } else {
80407             dontDeselect = (dir == 'up') ? startRow : endRow;
80408         }
80409         
80410         for (i = startRow; i <= endRow; i++){
80411             if (selectedCount == (endRow - startRow + 1)) {
80412                 if (i != dontDeselect) {
80413                     me.doDeselect(i, true);
80414                 }
80415             } else {
80416                 records.push(store.getAt(i));
80417             }
80418         }
80419         me.doMultiSelect(records, true);
80420     },
80421     
80422     /**
80423      * Selects a record instance by record instance or index.
80424      * @param {Ext.data.Model/Index} records An array of records or an index
80425      * @param {Boolean} keepExisting
80426      * @param {Boolean} suppressEvent Set to false to not fire a select event
80427      */
80428     select: function(records, keepExisting, suppressEvent) {
80429         this.doSelect(records, keepExisting, suppressEvent);
80430     },
80431
80432     /**
80433      * Deselects a record instance by record instance or index.
80434      * @param {Ext.data.Model/Index} records An array of records or an index
80435      * @param {Boolean} suppressEvent Set to false to not fire a deselect event
80436      */
80437     deselect: function(records, suppressEvent) {
80438         this.doDeselect(records, suppressEvent);
80439     },
80440     
80441     doSelect: function(records, keepExisting, suppressEvent) {
80442         var me = this,
80443             record;
80444             
80445         if (me.locked) {
80446             return;
80447         }
80448         if (typeof records === "number") {
80449             records = [me.store.getAt(records)];
80450         }
80451         if (me.selectionMode == "SINGLE" && records) {
80452             record = records.length ? records[0] : records;
80453             me.doSingleSelect(record, suppressEvent);
80454         } else {
80455             me.doMultiSelect(records, keepExisting, suppressEvent);
80456         }
80457     },
80458
80459     doMultiSelect: function(records, keepExisting, suppressEvent) {
80460         var me = this,
80461             selected = me.selected,
80462             change = false,
80463             i = 0,
80464             len, record;
80465             
80466         if (me.locked) {
80467             return;
80468         }
80469         
80470
80471         records = !Ext.isArray(records) ? [records] : records;
80472         len = records.length;
80473         if (!keepExisting && selected.getCount() > 0) {
80474             change = true;
80475             me.doDeselect(me.getSelection(), true);
80476         }
80477
80478         for (; i < len; i++) {
80479             record = records[i];
80480             if (keepExisting && me.isSelected(record)) {
80481                 continue;
80482             }
80483             change = true;
80484             me.lastSelected = record;
80485             selected.add(record);
80486
80487             me.onSelectChange(record, true, suppressEvent);
80488         }
80489         me.setLastFocused(record, suppressEvent);
80490         // fire selchange if there was a change and there is no suppressEvent flag
80491         me.maybeFireSelectionChange(change && !suppressEvent);
80492     },
80493
80494     // records can be an index, a record or an array of records
80495     doDeselect: function(records, suppressEvent) {
80496         var me = this,
80497             selected = me.selected,
80498             change = false,
80499             i = 0,
80500             len, record;
80501             
80502         if (me.locked) {
80503             return;
80504         }
80505
80506         if (typeof records === "number") {
80507             records = [me.store.getAt(records)];
80508         }
80509
80510         records = !Ext.isArray(records) ? [records] : records;
80511         len = records.length;
80512         for (; i < len; i++) {
80513             record = records[i];
80514             if (selected.remove(record)) {
80515                 if (me.lastSelected == record) {
80516                     me.lastSelected = selected.last();
80517                 }
80518                 me.onSelectChange(record, false, suppressEvent);
80519                 change = true;
80520             }
80521         }
80522         // fire selchange if there was a change and there is no suppressEvent flag
80523         me.maybeFireSelectionChange(change && !suppressEvent);
80524     },
80525
80526     doSingleSelect: function(record, suppressEvent) {
80527         var me = this,
80528             selected = me.selected;
80529             
80530         if (me.locked) {
80531             return;
80532         }
80533         // already selected.
80534         // should we also check beforeselect?
80535         if (me.isSelected(record)) {
80536             return;
80537         }
80538         if (selected.getCount() > 0) {
80539             me.doDeselect(me.lastSelected, suppressEvent);
80540         }
80541         selected.add(record);
80542         me.lastSelected = record;
80543         me.onSelectChange(record, true, suppressEvent);
80544         if (!suppressEvent) {
80545             me.setLastFocused(record);
80546         }
80547         me.maybeFireSelectionChange(!suppressEvent);
80548     },
80549
80550     /**
80551      * @param {Ext.data.Model} record
80552      * Set a record as the last focused record. This does NOT mean
80553      * that the record has been selected.
80554      */
80555     setLastFocused: function(record, supressFocus) {
80556         var me = this,
80557             recordBeforeLast = me.lastFocused;
80558         me.lastFocused = record;
80559         me.onLastFocusChanged(recordBeforeLast, record, supressFocus);
80560     },
80561     
80562     /**
80563      * Determines if this record is currently focused.
80564      * @param Ext.data.Record record
80565      */
80566     isFocused: function(record) {
80567         return record === this.getLastFocused();
80568     },
80569
80570
80571     // fire selection change as long as true is not passed
80572     // into maybeFireSelectionChange
80573     maybeFireSelectionChange: function(fireEvent) {
80574         if (fireEvent) {
80575             var me = this;
80576             me.fireEvent('selectionchange', me, me.getSelection());
80577         }
80578     },
80579
80580     /**
80581      * Returns the last selected record.
80582      */
80583     getLastSelected: function() {
80584         return this.lastSelected;
80585     },
80586     
80587     getLastFocused: function() {
80588         return this.lastFocused;
80589     },
80590
80591     /**
80592      * Returns an array of the currently selected records.
80593      */
80594     getSelection: function() {
80595         return this.selected.getRange();
80596     },
80597
80598     /**
80599      * Returns the current selectionMode. SINGLE, MULTI or SIMPLE.
80600      */
80601     getSelectionMode: function() {
80602         return this.selectionMode;
80603     },
80604
80605     /**
80606      * Sets the current selectionMode. SINGLE, MULTI or SIMPLE.
80607      */
80608     setSelectionMode: function(selMode) {
80609         selMode = selMode ? selMode.toUpperCase() : 'SINGLE';
80610         // set to mode specified unless it doesnt exist, in that case
80611         // use single.
80612         this.selectionMode = this.modes[selMode] ? selMode : 'SINGLE';
80613     },
80614
80615     /**
80616      * Returns true if the selections are locked.
80617      * @return {Boolean}
80618      */
80619     isLocked: function() {
80620         return this.locked;
80621     },
80622
80623     /**
80624      * Locks the current selection and disables any changes from
80625      * happening to the selection.
80626      * @param {Boolean} locked
80627      */
80628     setLocked: function(locked) {
80629         this.locked = !!locked;
80630     },
80631
80632     /**
80633      * Returns <tt>true</tt> if the specified row is selected.
80634      * @param {Record/Number} record The record or index of the record to check
80635      * @return {Boolean}
80636      */
80637     isSelected: function(record) {
80638         record = Ext.isNumber(record) ? this.store.getAt(record) : record;
80639         return this.selected.indexOf(record) !== -1;
80640     },
80641     
80642     /**
80643      * Returns true if there is a selected record.
80644      * @return {Boolean}
80645      */
80646     hasSelection: function() {
80647         return this.selected.getCount() > 0;
80648     },
80649
80650     refresh: function() {
80651         var me = this,
80652             toBeSelected = [],
80653             oldSelections = me.getSelection(),
80654             len = oldSelections.length,
80655             selection,
80656             change,
80657             i = 0,
80658             lastFocused = this.getLastFocused();
80659
80660         // check to make sure that there are no records
80661         // missing after the refresh was triggered, prune
80662         // them from what is to be selected if so
80663         for (; i < len; i++) {
80664             selection = oldSelections[i];
80665             if (!this.pruneRemoved || me.store.indexOf(selection) !== -1) {
80666                 toBeSelected.push(selection);
80667             }
80668         }
80669
80670         // there was a change from the old selected and
80671         // the new selection
80672         if (me.selected.getCount() != toBeSelected.length) {
80673             change = true;
80674         }
80675
80676         me.clearSelections();
80677         
80678         if (me.store.indexOf(lastFocused) !== -1) {
80679             // restore the last focus but supress restoring focus
80680             this.setLastFocused(lastFocused, true);
80681         }
80682
80683         if (toBeSelected.length) {
80684             // perform the selection again
80685             me.doSelect(toBeSelected, false, true);
80686         }
80687         
80688         me.maybeFireSelectionChange(change);
80689     },
80690
80691     clearSelections: function() {
80692         // reset the entire selection to nothing
80693         var me = this;
80694         me.selected.clear();
80695         me.lastSelected = null;
80696         me.setLastFocused(null);
80697     },
80698
80699     // when a record is added to a store
80700     onStoreAdd: function() {
80701
80702     },
80703
80704     // when a store is cleared remove all selections
80705     // (if there were any)
80706     onStoreClear: function() {
80707         var me = this,
80708             selected = this.selected;
80709             
80710         if (selected.getCount > 0) {
80711             selected.clear();
80712             me.lastSelected = null;
80713             me.setLastFocused(null);
80714             me.maybeFireSelectionChange(true);
80715         }
80716     },
80717
80718     // prune records from the SelectionModel if
80719     // they were selected at the time they were
80720     // removed.
80721     onStoreRemove: function(store, record) {
80722         var me = this,
80723             selected = me.selected;
80724             
80725         if (me.locked || !me.pruneRemoved) {
80726             return;
80727         }
80728
80729         if (selected.remove(record)) {
80730             if (me.lastSelected == record) {
80731                 me.lastSelected = null;
80732             }
80733             if (me.getLastFocused() == record) {
80734                 me.setLastFocused(null);
80735             }
80736             me.maybeFireSelectionChange(true);
80737         }
80738     },
80739
80740     getCount: function() {
80741         return this.selected.getCount();
80742     },
80743
80744     // cleanup.
80745     destroy: function() {
80746
80747     },
80748
80749     // if records are updated
80750     onStoreUpdate: function() {
80751
80752     },
80753
80754     // @abstract
80755     onSelectChange: function(record, isSelected, suppressEvent) {
80756
80757     },
80758
80759     // @abstract
80760     onLastFocusChanged: function(oldFocused, newFocused) {
80761
80762     },
80763
80764     // @abstract
80765     onEditorKey: function(field, e) {
80766
80767     },
80768
80769     // @abstract
80770     bindComponent: function(cmp) {
80771
80772     }
80773 });
80774 /**
80775  * @class Ext.selection.DataViewModel
80776  * @ignore
80777  */
80778 Ext.define('Ext.selection.DataViewModel', {
80779     extend: 'Ext.selection.Model',
80780     
80781     requires: ['Ext.util.KeyNav'],
80782
80783     deselectOnContainerClick: true,
80784     
80785     /**
80786      * @cfg {Boolean} enableKeyNav
80787      * 
80788      * Turns on/off keyboard navigation within the DataView. Defaults to true.
80789      */
80790     enableKeyNav: true,
80791     
80792     constructor: function(cfg){
80793         this.addEvents(
80794             /**
80795              * @event deselect
80796              * Fired after a record is deselected
80797              * @param {Ext.selection.DataViewModel} this
80798              * @param  {Ext.data.Model} record The deselected record
80799              */
80800             'deselect',
80801             
80802             /**
80803              * @event select
80804              * Fired after a record is selected
80805              * @param {Ext.selection.DataViewModel} this
80806              * @param  {Ext.data.Model} record The selected record
80807              */
80808             'select'
80809         );
80810         this.callParent(arguments);
80811     },
80812     
80813     bindComponent: function(view) {
80814         var me = this,
80815             eventListeners = {
80816                 refresh: me.refresh,
80817                 scope: me
80818             };
80819
80820         me.view = view;
80821         me.bind(view.getStore());
80822
80823         view.on(view.triggerEvent, me.onItemClick, me);
80824         view.on(view.triggerCtEvent, me.onContainerClick, me);
80825
80826         view.on(eventListeners);
80827
80828         if (me.enableKeyNav) {
80829             me.initKeyNav(view);
80830         }
80831     },
80832
80833     onItemClick: function(view, record, item, index, e) {
80834         this.selectWithEvent(record, e);
80835     },
80836
80837     onContainerClick: function() {
80838         if (this.deselectOnContainerClick) {
80839             this.deselectAll();
80840         }
80841     },
80842     
80843     initKeyNav: function(view) {
80844         var me = this;
80845         
80846         if (!view.rendered) {
80847             view.on('render', Ext.Function.bind(me.initKeyNav, me, [view], 0), me, {single: true});
80848             return;
80849         }
80850         
80851         view.el.set({
80852             tabIndex: -1
80853         });
80854         me.keyNav = Ext.create('Ext.util.KeyNav', view.el, {
80855             down: Ext.pass(me.onNavKey, [1], me),
80856             right: Ext.pass(me.onNavKey, [1], me),
80857             left: Ext.pass(me.onNavKey, [-1], me),
80858             up: Ext.pass(me.onNavKey, [-1], me),
80859             scope: me
80860         });
80861     },
80862     
80863     onNavKey: function(step) {
80864         step = step || 1;
80865         var me = this,
80866             view = me.view,
80867             selected = me.getSelection()[0],
80868             numRecords = me.view.store.getCount(),
80869             idx;
80870                 
80871         if (selected) {
80872             idx = view.indexOf(view.getNode(selected)) + step;
80873         } else {
80874             idx = 0;
80875         }
80876         
80877         if (idx < 0) {
80878             idx = numRecords - 1;
80879         } else if (idx >= numRecords) {
80880             idx = 0;
80881         }
80882         
80883         me.select(idx);
80884     },
80885
80886     // Allow the DataView to update the ui
80887     onSelectChange: function(record, isSelected, suppressEvent) {
80888         var me = this,
80889             view = me.view,
80890             allowSelect = true;
80891         
80892         if (isSelected) {
80893             if (!suppressEvent) {
80894                 allowSelect = me.fireEvent('beforeselect', me, record) !== false;
80895             }
80896             if (allowSelect) {
80897                 view.onItemSelect(record);
80898                 if (!suppressEvent) {
80899                     me.fireEvent('select', me, record);
80900                 }
80901             }
80902         } else {
80903             view.onItemDeselect(record);
80904             if (!suppressEvent) {
80905                 me.fireEvent('deselect', me, record);
80906             }
80907         }
80908     }
80909 });
80910
80911 /**
80912  * @class Ext.state.CookieProvider
80913  * @extends Ext.state.Provider
80914  * A Provider implementation which saves and retrieves state via cookies.
80915  * The CookieProvider supports the usual cookie options, such as:
80916  * <ul>
80917  * <li>{@link #path}</li>
80918  * <li>{@link #expires}</li>
80919  * <li>{@link #domain}</li>
80920  * <li>{@link #secure}</li>
80921  * </ul>
80922  <pre><code>
80923    var cp = new Ext.state.CookieProvider({
80924        path: "/cgi-bin/",
80925        expires: new Date(new Date().getTime()+(1000*60*60*24*30)), //30 days
80926        domain: "sencha.com"
80927    });
80928    Ext.state.Manager.setProvider(cp);
80929  </code></pre>
80930  
80931  
80932  * @cfg {String} path The path for which the cookie is active (defaults to root '/' which makes it active for all pages in the site)
80933  * @cfg {Date} expires The cookie expiration date (defaults to 7 days from now)
80934  * @cfg {String} domain The domain to save the cookie for.  Note that you cannot specify a different domain than
80935  * your page is on, but you can specify a sub-domain, or simply the domain itself like 'sencha.com' to include
80936  * all sub-domains if you need to access cookies across different sub-domains (defaults to null which uses the same
80937  * domain the page is running on including the 'www' like 'www.sencha.com')
80938  * @cfg {Boolean} secure True if the site is using SSL (defaults to false)
80939  * @constructor
80940  * Create a new CookieProvider
80941  * @param {Object} config The configuration object
80942  */
80943 Ext.define('Ext.state.CookieProvider', {
80944     extend: 'Ext.state.Provider',
80945
80946     constructor : function(config){
80947         var me = this;
80948         me.path = "/";
80949         me.expires = new Date(new Date().getTime()+(1000*60*60*24*7)); //7 days
80950         me.domain = null;
80951         me.secure = false;
80952         me.callParent(arguments);
80953         me.state = me.readCookies();
80954     },
80955     
80956     // private
80957     set : function(name, value){
80958         var me = this;
80959         
80960         if(typeof value == "undefined" || value === null){
80961             me.clear(name);
80962             return;
80963         }
80964         me.setCookie(name, value);
80965         me.callParent(arguments);
80966     },
80967
80968     // private
80969     clear : function(name){
80970         this.clearCookie(name);
80971         this.callParent(arguments);
80972     },
80973
80974     // private
80975     readCookies : function(){
80976         var cookies = {},
80977             c = document.cookie + ";",
80978             re = /\s?(.*?)=(.*?);/g,
80979             prefix = this.prefix,
80980             len = prefix.length,
80981             matches,
80982             name,
80983             value;
80984             
80985         while((matches = re.exec(c)) != null){
80986             name = matches[1];
80987             value = matches[2];
80988             if (name && name.substring(0, len) == prefix){
80989                 cookies[name.substr(len)] = this.decodeValue(value);
80990             }
80991         }
80992         return cookies;
80993     },
80994
80995     // private
80996     setCookie : function(name, value){
80997         var me = this;
80998         
80999         document.cookie = me.prefix + name + "=" + me.encodeValue(value) +
81000            ((me.expires == null) ? "" : ("; expires=" + me.expires.toGMTString())) +
81001            ((me.path == null) ? "" : ("; path=" + me.path)) +
81002            ((me.domain == null) ? "" : ("; domain=" + me.domain)) +
81003            ((me.secure == true) ? "; secure" : "");
81004     },
81005
81006     // private
81007     clearCookie : function(name){
81008         var me = this;
81009         
81010         document.cookie = me.prefix + name + "=null; expires=Thu, 01-Jan-70 00:00:01 GMT" +
81011            ((me.path == null) ? "" : ("; path=" + me.path)) +
81012            ((me.domain == null) ? "" : ("; domain=" + me.domain)) +
81013            ((me.secure == true) ? "; secure" : "");
81014     }
81015 });
81016
81017 Ext.define('Ext.state.LocalStorageProvider', {
81018     /* Begin Definitions */
81019     
81020     extend: 'Ext.state.Provider',
81021     
81022     alias: 'state.localstorage',
81023     
81024     /* End Definitions */
81025    
81026     constructor: function(){
81027         var me = this;
81028         me.callParent(arguments);
81029         me.store = me.getStorageObject();
81030         me.state = me.readLocalStorage();
81031     },
81032     
81033     readLocalStorage: function(){
81034         var store = this.store,
81035             i = 0,
81036             len = store.length,
81037             prefix = this.prefix,
81038             prefixLen = prefix.length,
81039             data = {},
81040             key;
81041             
81042         for (; i < len; ++i) {
81043             key = store.key(i);
81044             if (key.substring(0, prefixLen) == prefix) {
81045                 data[key.substr(prefixLen)] = this.decodeValue(store.getItem(key));
81046             }            
81047         }
81048         return data;
81049     },
81050     
81051     set : function(name, value){
81052         var me = this;
81053         
81054         me.clear(name);
81055         if (typeof value == "undefined" || value === null) {
81056             return;
81057         }
81058         me.store.setItem(me.prefix + name, me.encodeValue(value));
81059         me.callParent(arguments);
81060     },
81061
81062     // private
81063     clear : function(name){
81064         this.store.removeItem(this.prefix + name);
81065         this.callParent(arguments);
81066     },
81067     
81068     getStorageObject: function(){
81069         try {
81070             var supports = 'localStorage' in window && window['localStorage'] !== null;
81071             if (supports) {
81072                 return window.localStorage;
81073             }
81074         } catch (e) {
81075             return false;
81076         }
81077         Ext.Error.raise('LocalStorage is not supported by the current browser');
81078     }    
81079 });
81080
81081 /**
81082  * @class Ext.util.Point
81083  * @extends Ext.util.Region
81084  *
81085  * Represents a 2D point with x and y properties, useful for comparison and instantiation
81086  * from an event:
81087  * <pre><code>
81088  * var point = Ext.util.Point.fromEvent(e);
81089  * </code></pre>
81090  */
81091
81092 Ext.define('Ext.util.Point', {
81093
81094     /* Begin Definitions */
81095     extend: 'Ext.util.Region',
81096
81097     statics: {
81098
81099         /**
81100          * Returns a new instance of Ext.util.Point base on the pageX / pageY values of the given event
81101          * @static
81102          * @param {Event} e The event
81103          * @returns Ext.util.Point
81104          */
81105         fromEvent: function(e) {
81106             e = (e.changedTouches && e.changedTouches.length > 0) ? e.changedTouches[0] : e;
81107             return new this(e.pageX, e.pageY);
81108         }
81109     },
81110
81111     /* End Definitions */
81112
81113     constructor: function(x, y) {
81114         this.callParent([y, x, y, x]);
81115     },
81116
81117     /**
81118      * Returns a human-eye-friendly string that represents this point,
81119      * useful for debugging
81120      * @return {String}
81121      */
81122     toString: function() {
81123         return "Point[" + this.x + "," + this.y + "]";
81124     },
81125
81126     /**
81127      * Compare this point and another point
81128      * @param {Ext.util.Point/Object} The point to compare with, either an instance
81129      * of Ext.util.Point or an object with left and top properties
81130      * @return {Boolean} Returns whether they are equivalent
81131      */
81132     equals: function(p) {
81133         return (this.x == p.x && this.y == p.y);
81134     },
81135
81136     /**
81137      * Whether the given point is not away from this point within the given threshold amount.
81138      * TODO: Rename this isNear.
81139      * @param {Ext.util.Point/Object} The point to check with, either an instance
81140      * of Ext.util.Point or an object with left and top properties
81141      * @param {Object/Number} threshold Can be either an object with x and y properties or a number
81142      * @return {Boolean}
81143      */
81144     isWithin: function(p, threshold) {
81145         if (!Ext.isObject(threshold)) {
81146             threshold = {
81147                 x: threshold,
81148                 y: threshold
81149             };
81150         }
81151
81152         return (this.x <= p.x + threshold.x && this.x >= p.x - threshold.x &&
81153                 this.y <= p.y + threshold.y && this.y >= p.y - threshold.y);
81154     },
81155
81156     /**
81157      * Compare this point with another point when the x and y values of both points are rounded. E.g:
81158      * [100.3,199.8] will equals to [100, 200]
81159      * @param {Ext.util.Point/Object} The point to compare with, either an instance
81160      * of Ext.util.Point or an object with x and y properties
81161      * @return {Boolean}
81162      */
81163     roundedEquals: function(p) {
81164         return (Math.round(this.x) == Math.round(p.x) && Math.round(this.y) == Math.round(p.y));
81165     }
81166 }, function() {
81167     /**
81168      * Translate this region by the given offset amount. TODO: Either use translate or translateBy!
81169      * @param {Ext.util.Offset/Object} offset Object containing the <code>x</code> and <code>y</code> properties.
81170      * Or the x value is using the two argument form.
81171      * @param {Number} The y value unless using an Offset object.
81172      * @return {Ext.util.Region} this This Region
81173      */
81174     this.prototype.translate = Ext.util.Region.prototype.translateBy;
81175 });
81176
81177 /**
81178  * @class Ext.view.AbstractView
81179  * @extends Ext.Component
81180  * This is an abstract superclass and should not be used directly. Please see {@link Ext.view.View}.
81181  */
81182 Ext.define('Ext.view.AbstractView', {
81183     extend: 'Ext.Component',
81184     alternateClassName: 'Ext.view.AbstractView',
81185     requires: [
81186         'Ext.LoadMask',
81187         'Ext.data.StoreManager',
81188         'Ext.CompositeElementLite',
81189         'Ext.DomQuery',
81190         'Ext.selection.DataViewModel'
81191     ],
81192     
81193     inheritableStatics: {
81194         getRecord: function(node) {
81195             return this.getBoundView(node).getRecord(node);
81196         },
81197         
81198         getBoundView: function(node) {
81199             return Ext.getCmp(node.boundView);
81200         }
81201     },
81202     
81203     /**
81204      * @cfg {String/Array/Ext.XTemplate} tpl
81205      * @required
81206      * The HTML fragment or an array of fragments that will make up the template used by this DataView.  This should
81207      * be specified in the same format expected by the constructor of {@link Ext.XTemplate}.
81208      */
81209     /**
81210      * @cfg {Ext.data.Store} store
81211      * @required
81212      * The {@link Ext.data.Store} to bind this DataView to.
81213      */
81214
81215     /**
81216      * @cfg {String} itemSelector
81217      * @required
81218      * <b>This is a required setting</b>. A simple CSS selector (e.g. <tt>div.some-class</tt> or
81219      * <tt>span:first-child</tt>) that will be used to determine what nodes this DataView will be
81220      * working with. The itemSelector is used to map DOM nodes to records. As such, there should
81221      * only be one root level element that matches the selector for each record.
81222      */
81223     
81224     /**
81225      * @cfg {String} itemCls
81226      * Specifies the class to be assigned to each element in the view when used in conjunction with the
81227      * {@link #itemTpl} configuration.
81228      */
81229     itemCls: Ext.baseCSSPrefix + 'dataview-item',
81230     
81231     /**
81232      * @cfg {String/Array/Ext.XTemplate} itemTpl
81233      * The inner portion of the item template to be rendered. Follows an XTemplate
81234      * structure and will be placed inside of a tpl.
81235      */
81236
81237     /**
81238      * @cfg {String} overItemCls
81239      * A CSS class to apply to each item in the view on mouseover (defaults to undefined). 
81240      * Ensure {@link #trackOver} is set to `true` to make use of this.
81241      */
81242
81243     /**
81244      * @cfg {String} loadingText
81245      * A string to display during data load operations (defaults to undefined).  If specified, this text will be
81246      * displayed in a loading div and the view's contents will be cleared while loading, otherwise the view's
81247      * contents will continue to display normally until the new data is loaded and the contents are replaced.
81248      */
81249     loadingText: 'Loading...',
81250     
81251     /**
81252      * @cfg {String} loadingCls
81253      * The CSS class to apply to the loading message element (defaults to Ext.LoadMask.prototype.msgCls "x-mask-loading")
81254      */
81255     
81256     /**
81257      * @cfg {Boolean} loadingUseMsg
81258      * Whether or not to use the loading message.
81259      * @private
81260      */
81261     loadingUseMsg: true,
81262     
81263
81264     /**
81265      * @cfg {Number} loadingHeight
81266      * If specified, gives an explicit height for the data view when it is showing the {@link #loadingText},
81267      * if that is specified. This is useful to prevent the view's height from collapsing to zero when the
81268      * loading mask is applied and there are no other contents in the data view. Defaults to undefined.
81269      */
81270
81271     /**
81272      * @cfg {String} selectedItemCls
81273      * A CSS class to apply to each selected item in the view (defaults to 'x-view-selected').
81274      */
81275     selectedItemCls: Ext.baseCSSPrefix + 'item-selected',
81276
81277     /**
81278      * @cfg {String} emptyText
81279      * The text to display in the view when there is no data to display (defaults to '').
81280      * Note that when using local data the emptyText will not be displayed unless you set
81281      * the {@link #deferEmptyText} option to false.
81282      */
81283     emptyText: "",
81284
81285     /**
81286      * @cfg {Boolean} deferEmptyText True to defer emptyText being applied until the store's first load
81287      */
81288     deferEmptyText: true,
81289
81290     /**
81291      * @cfg {Boolean} trackOver True to enable mouseenter and mouseleave events
81292      */
81293     trackOver: false,
81294
81295     /**
81296      * @cfg {Boolean} blockRefresh Set this to true to ignore datachanged events on the bound store. This is useful if
81297      * you wish to provide custom transition animations via a plugin (defaults to false)
81298      */
81299     blockRefresh: false,
81300
81301     /**
81302      * @cfg {Boolean} disableSelection <p><tt>true</tt> to disable selection within the DataView. Defaults to <tt>false</tt>.
81303      * This configuration will lock the selection model that the DataView uses.</p>
81304      */
81305
81306
81307     //private
81308     last: false,
81309     
81310     triggerEvent: 'itemclick',
81311     triggerCtEvent: 'containerclick',
81312     
81313     addCmpEvents: function() {
81314         
81315     },
81316
81317     // private
81318     initComponent : function(){
81319         var me = this,
81320             isDef = Ext.isDefined,
81321             itemTpl = me.itemTpl,
81322             memberFn = {};
81323             
81324         if (itemTpl) {
81325             if (Ext.isArray(itemTpl)) {
81326                 // string array
81327                 itemTpl = itemTpl.join('');
81328             } else if (Ext.isObject(itemTpl)) {
81329                 // tpl instance
81330                 memberFn = Ext.apply(memberFn, itemTpl.initialConfig);
81331                 itemTpl = itemTpl.html;
81332             }
81333             
81334             if (!me.itemSelector) {
81335                 me.itemSelector = '.' + me.itemCls;
81336             }
81337             
81338             itemTpl = Ext.String.format('<tpl for="."><div class="{0}">{1}</div></tpl>', me.itemCls, itemTpl);
81339             me.tpl = Ext.create('Ext.XTemplate', itemTpl, memberFn);
81340         }
81341
81342         if (!isDef(me.tpl) || !isDef(me.itemSelector)) {
81343             Ext.Error.raise({
81344                 sourceClass: 'Ext.view.View',
81345                 tpl: me.tpl,
81346                 itemSelector: me.itemSelector,
81347                 msg: "DataView requires both tpl and itemSelector configurations to be defined."
81348             });
81349         }
81350
81351         me.callParent();
81352         if(Ext.isString(me.tpl) || Ext.isArray(me.tpl)){
81353             me.tpl = Ext.create('Ext.XTemplate', me.tpl);
81354         }
81355
81356         // backwards compat alias for overClass/selectedClass
81357         // TODO: Consider support for overCls generation Ext.Component config
81358         if (isDef(me.overCls) || isDef(me.overClass)) {
81359             if (Ext.isDefined(Ext.global.console)) {
81360                 Ext.global.console.warn('Ext.view.View: Using the deprecated overCls or overClass configuration. Use overItemCls instead.');
81361             }
81362             me.overItemCls = me.overCls || me.overClass;
81363             delete me.overCls;
81364             delete me.overClass;
81365         }
81366
81367         if (me.overItemCls) {
81368             me.trackOver = true;
81369         }
81370         
81371         if (isDef(me.selectedCls) || isDef(me.selectedClass)) {
81372             if (Ext.isDefined(Ext.global.console)) {
81373                 Ext.global.console.warn('Ext.view.View: Using the deprecated selectedCls or selectedClass configuration. Use selectedItemCls instead.');
81374             }
81375             me.selectedItemCls = me.selectedCls || me.selectedClass;
81376             delete me.selectedCls;
81377             delete me.selectedClass;
81378         }
81379         
81380         me.addEvents(
81381             /**
81382              * @event beforerefresh
81383              * Fires before the view is refreshed
81384              * @param {Ext.view.View} this The DataView object
81385              */
81386             'beforerefresh',
81387             /**
81388              * @event refresh
81389              * Fires when the view is refreshed
81390              * @param {Ext.view.View} this The DataView object
81391              */
81392             'refresh',
81393             /**
81394              * @event itemupdate
81395              * Fires when the node associated with an individual record is updated
81396              * @param {Ext.data.Model} record The model instance
81397              * @param {Number} index The index of the record/node
81398              * @param {HTMLElement} node The node that has just been updated
81399              */
81400             'itemupdate',
81401             /**
81402              * @event itemadd
81403              * Fires when the nodes associated with an recordset have been added to the underlying store
81404              * @param {Array[Ext.data.Model]} records The model instance
81405              * @param {Number} index The index at which the set of record/nodes starts
81406              * @param {Array[HTMLElement]} node The node that has just been updated
81407              */
81408             'itemadd',
81409             /**
81410              * @event itemremove
81411              * Fires when the node associated with an individual record is removed
81412              * @param {Ext.data.Model} record The model instance
81413              * @param {Number} index The index of the record/node
81414              */
81415             'itemremove'
81416         );
81417
81418         me.addCmpEvents();
81419
81420         if (me.store) {
81421             me.store = Ext.data.StoreManager.lookup(me.store);
81422         }
81423         me.all = new Ext.CompositeElementLite();
81424         me.getSelectionModel().bindComponent(me);
81425     },
81426
81427     onRender: function() {
81428         var me = this,
81429             loadingText = me.loadingText,
81430             loadingHeight = me.loadingHeight,
81431             undef;
81432
81433         me.callParent(arguments);
81434         if (loadingText) {
81435             
81436             // Attach the LoadMask to a *Component* so that it can be sensitive to resizing during long loads.
81437             // If this DataView is floating, then mask this DataView.
81438             // Otherwise, mask its owning Container (or this, if there *is* no owning Container).
81439             // LoadMask captures the element upon render.
81440             me.loadMask = Ext.create('Ext.LoadMask', me.floating ? me : me.ownerCt || me, {
81441                 msg: loadingText,
81442                 msgCls: me.loadingCls,
81443                 useMsg: me.loadingUseMsg,
81444                 listeners: {
81445                     beforeshow: function() {
81446                         me.getTargetEl().update('');
81447                         me.getSelectionModel().deselectAll();
81448                         me.all.clear();
81449                         if (loadingHeight) {
81450                             me.setCalculatedSize(undef, loadingHeight);
81451                         }
81452                     },
81453                     hide: function() {
81454                         if (loadingHeight) {
81455                             me.setHeight(me.height);
81456                         }
81457                     }
81458                 }
81459             });
81460         }
81461     },
81462
81463     getSelectionModel: function(){
81464         var me = this,
81465             mode = 'SINGLE';
81466
81467         if (!me.selModel) {
81468             me.selModel = {};
81469         }
81470
81471         if (me.simpleSelect) {
81472             mode = 'SIMPLE';
81473         } else if (me.multiSelect) {
81474             mode = 'MULTI';
81475         }
81476
81477         Ext.applyIf(me.selModel, {
81478             allowDeselect: me.allowDeselect,
81479             mode: mode
81480         });
81481
81482         if (!me.selModel.events) {
81483             me.selModel = Ext.create('Ext.selection.DataViewModel', me.selModel);
81484         }
81485
81486         if (!me.selModel.hasRelaySetup) {
81487             me.relayEvents(me.selModel, ['selectionchange', 'beforeselect', 'select', 'deselect']);
81488             me.selModel.hasRelaySetup = true;
81489         }
81490
81491         // lock the selection model if user
81492         // has disabled selection
81493         if (me.disableSelection) {
81494             me.selModel.locked = true;
81495         }
81496
81497         return me.selModel;
81498     },
81499
81500     /**
81501      * Refreshes the view by reloading the data from the store and re-rendering the template.
81502      */
81503     refresh: function() {
81504         var me = this,
81505             el,
81506             records;
81507             
81508         if (!me.rendered) {
81509             return;
81510         }
81511         
81512         me.fireEvent('beforerefresh', me);
81513         el = me.getTargetEl();
81514         records = me.store.getRange();
81515
81516         el.update('');
81517         if (records.length < 1) {
81518             if (!me.deferEmptyText || me.hasSkippedEmptyText) {
81519                 el.update(me.emptyText);
81520             }
81521             me.all.clear();
81522         } else {
81523             me.tpl.overwrite(el, me.collectData(records, 0));
81524             me.all.fill(Ext.query(me.getItemSelector(), el.dom));
81525             me.updateIndexes(0);
81526         }
81527         
81528         me.selModel.refresh();
81529         me.hasSkippedEmptyText = true;
81530         me.fireEvent('refresh', me);
81531     },
81532
81533     /**
81534      * Function which can be overridden to provide custom formatting for each Record that is used by this
81535      * DataView's {@link #tpl template} to render each node.
81536      * @param {Array/Object} data The raw data object that was used to create the Record.
81537      * @param {Number} recordIndex the index number of the Record being prepared for rendering.
81538      * @param {Record} record The Record being prepared for rendering.
81539      * @return {Array/Object} The formatted data in a format expected by the internal {@link #tpl template}'s overwrite() method.
81540      * (either an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'}))
81541      */
81542     prepareData: function(data, index, record) {
81543         if (record) {    
81544             Ext.apply(data, record.getAssociatedData());            
81545         }
81546         return data;
81547     },
81548     
81549     /**
81550      * <p>Function which can be overridden which returns the data object passed to this
81551      * DataView's {@link #tpl template} to render the whole DataView.</p>
81552      * <p>This is usually an Array of data objects, each element of which is processed by an
81553      * {@link Ext.XTemplate XTemplate} which uses <tt>'&lt;tpl for="."&gt;'</tt> to iterate over its supplied
81554      * data object as an Array. However, <i>named</i> properties may be placed into the data object to
81555      * provide non-repeating data such as headings, totals etc.</p>
81556      * @param {Array} records An Array of {@link Ext.data.Model}s to be rendered into the DataView.
81557      * @param {Number} startIndex the index number of the Record being prepared for rendering.
81558      * @return {Array} An Array of data objects to be processed by a repeating XTemplate. May also
81559      * contain <i>named</i> properties.
81560      */
81561     collectData : function(records, startIndex){
81562         var r = [],
81563             i = 0,
81564             len = records.length;
81565
81566         for(; i < len; i++){
81567             r[r.length] = this.prepareData(records[i].data, startIndex + i, records[i]);
81568         }
81569
81570         return r;
81571     },
81572
81573     // private
81574     bufferRender : function(records, index){
81575         var div = document.createElement('div');
81576         this.tpl.overwrite(div, this.collectData(records, index));
81577         return Ext.query(this.getItemSelector(), div);
81578     },
81579
81580     // private
81581     onUpdate : function(ds, record){
81582         var me = this,
81583             index = me.store.indexOf(record),
81584             original,
81585             node;
81586
81587         if (index > -1){
81588             original = me.all.elements[index];
81589             node = me.bufferRender([record], index)[0];
81590
81591             me.all.replaceElement(index, node, true);
81592             me.updateIndexes(index, index);
81593
81594             // Maintain selection after update
81595             // TODO: Move to approriate event handler.
81596             me.selModel.refresh();
81597             me.fireEvent('itemupdate', record, index, node);
81598         }
81599
81600     },
81601
81602     // private
81603     onAdd : function(ds, records, index) {
81604         var me = this,
81605             nodes;
81606             
81607         if (me.all.getCount() === 0) {
81608             me.refresh();
81609             return;
81610         }
81611         
81612         nodes = me.bufferRender(records, index);
81613         me.doAdd(nodes, records, index);
81614
81615         me.selModel.refresh();
81616         me.updateIndexes(index);
81617         me.fireEvent('itemadd', records, index, nodes);
81618     },
81619
81620     doAdd: function(nodes, records, index) {
81621         var n, a = this.all.elements;
81622         if (index < this.all.getCount()) {
81623             n = this.all.item(index).insertSibling(nodes, 'before', true);
81624             a.splice.apply(a, [index, 0].concat(nodes));
81625         } 
81626         else {
81627             n = this.all.last().insertSibling(nodes, 'after', true);
81628             a.push.apply(a, nodes);
81629         }    
81630     },
81631     
81632     // private
81633     onRemove : function(ds, record, index) {
81634         var me = this;
81635         
81636         me.doRemove(record, index);
81637         me.updateIndexes(index);
81638         if (me.store.getCount() === 0){
81639             me.refresh();
81640         }
81641         me.fireEvent('itemremove', record, index);
81642     },
81643     
81644     doRemove: function(record, index) {
81645         this.all.removeElement(index, true);
81646     },
81647
81648     /**
81649      * Refreshes an individual node's data from the store.
81650      * @param {Number} index The item's data index in the store
81651      */
81652     refreshNode : function(index){
81653         this.onUpdate(this.store, this.store.getAt(index));
81654     },
81655
81656     // private
81657     updateIndexes : function(startIndex, endIndex) {
81658         var ns = this.all.elements,
81659             records = this.store.getRange();
81660         startIndex = startIndex || 0;
81661         endIndex = endIndex || ((endIndex === 0) ? 0 : (ns.length - 1));
81662         for(var i = startIndex; i <= endIndex; i++){
81663             ns[i].viewIndex = i;
81664             ns[i].viewRecordId = records[i].internalId;
81665             if (!ns[i].boundView) {
81666                 ns[i].boundView = this.id;
81667             }
81668         }
81669     },
81670
81671     /**
81672      * Returns the store associated with this DataView.
81673      * @return {Ext.data.Store} The store
81674      */
81675     getStore : function(){
81676         return this.store;
81677     },
81678
81679     /**
81680      * Changes the data store bound to this view and refreshes it.
81681      * @param {Store} store The store to bind to this view
81682      */
81683     bindStore : function(store, initial) {
81684         var me = this;
81685         
81686         if (!initial && me.store) {
81687             if (store !== me.store && me.store.autoDestroy) {
81688                 me.store.destroy();
81689             } 
81690             else {
81691                 me.mun(me.store, {
81692                     scope: me,
81693                     datachanged: me.onDataChanged,
81694                     add: me.onAdd,
81695                     remove: me.onRemove,
81696                     update: me.onUpdate,
81697                     clear: me.refresh
81698                 });
81699             }
81700             if (!store) {
81701                 if (me.loadMask) {
81702                     me.loadMask.bindStore(null);
81703                 }
81704                 me.store = null;
81705             }
81706         }
81707         if (store) {
81708             store = Ext.data.StoreManager.lookup(store);
81709             me.mon(store, {
81710                 scope: me,
81711                 datachanged: me.onDataChanged,
81712                 add: me.onAdd,
81713                 remove: me.onRemove,
81714                 update: me.onUpdate,
81715                 clear: me.refresh
81716             });
81717             if (me.loadMask) {
81718                 me.loadMask.bindStore(store);
81719             }
81720         }
81721         
81722         me.store = store;
81723         // Bind the store to our selection model
81724         me.getSelectionModel().bind(store);
81725         
81726         if (store) {
81727             me.refresh(true);
81728         }
81729     },
81730
81731     /**
81732      * @private
81733      * Calls this.refresh if this.blockRefresh is not true
81734      */
81735     onDataChanged: function() {
81736         if (this.blockRefresh !== true) {
81737             this.refresh.apply(this, arguments);
81738         }
81739     },
81740
81741     /**
81742      * Returns the template node the passed child belongs to, or null if it doesn't belong to one.
81743      * @param {HTMLElement} node
81744      * @return {HTMLElement} The template node
81745      */
81746     findItemByChild: function(node){
81747         return Ext.fly(node).findParent(this.getItemSelector(), this.getTargetEl());
81748     },
81749     
81750     /**
81751      * Returns the template node by the Ext.EventObject or null if it is not found.
81752      * @param {Ext.EventObject} e
81753      */
81754     findTargetByEvent: function(e) {
81755         return e.getTarget(this.getItemSelector(), this.getTargetEl());
81756     },
81757
81758
81759     /**
81760      * Gets the currently selected nodes.
81761      * @return {Array} An array of HTMLElements
81762      */
81763     getSelectedNodes: function(){
81764         var nodes   = [],
81765             records = this.selModel.getSelection(),
81766             ln = records.length,
81767             i  = 0;
81768
81769         for (; i < ln; i++) {
81770             nodes.push(this.getNode(records[i]));
81771         }
81772
81773         return nodes;
81774     },
81775
81776     /**
81777      * Gets an array of the records from an array of nodes
81778      * @param {Array} nodes The nodes to evaluate
81779      * @return {Array} records The {@link Ext.data.Model} objects
81780      */
81781     getRecords: function(nodes) {
81782         var records = [],
81783             i = 0,
81784             len = nodes.length,
81785             data = this.store.data;
81786
81787         for (; i < len; i++) {
81788             records[records.length] = data.getByKey(nodes[i].viewRecordId);
81789         }
81790
81791         return records;
81792     },
81793
81794     /**
81795      * Gets a record from a node
81796      * @param {Element/HTMLElement} node The node to evaluate
81797      * 
81798      * @return {Record} record The {@link Ext.data.Model} object
81799      */
81800     getRecord: function(node){
81801         return this.store.data.getByKey(Ext.getDom(node).viewRecordId);
81802     },
81803     
81804
81805     /**
81806      * Returns true if the passed node is selected, else false.
81807      * @param {HTMLElement/Number/Ext.data.Model} node The node, node index or record to check
81808      * @return {Boolean} True if selected, else false
81809      */
81810     isSelected : function(node) {
81811         // TODO: El/Idx/Record
81812         var r = this.getRecord(node);
81813         return this.selModel.isSelected(r);
81814     },
81815     
81816     /**
81817      * Selects a record instance by record instance or index.
81818      * @param {Ext.data.Model/Index} records An array of records or an index
81819      * @param {Boolean} keepExisting
81820      * @param {Boolean} suppressEvent Set to false to not fire a select event
81821      */
81822     select: function(records, keepExisting, suppressEvent) {
81823         this.selModel.select(records, keepExisting, suppressEvent);
81824     },
81825
81826     /**
81827      * Deselects a record instance by record instance or index.
81828      * @param {Ext.data.Model/Index} records An array of records or an index
81829      * @param {Boolean} suppressEvent Set to false to not fire a deselect event
81830      */
81831     deselect: function(records, suppressEvent) {
81832         this.selModel.deselect(records, suppressEvent);
81833     },
81834
81835     /**
81836      * Gets a template node.
81837      * @param {HTMLElement/String/Number/Ext.data.Model} nodeInfo An HTMLElement template node, index of a template node,
81838      * the id of a template node or the record associated with the node.
81839      * @return {HTMLElement} The node or null if it wasn't found
81840      */
81841     getNode : function(nodeInfo) {
81842         if (Ext.isString(nodeInfo)) {
81843             return document.getElementById(nodeInfo);
81844         } else if (Ext.isNumber(nodeInfo)) {
81845             return this.all.elements[nodeInfo];
81846         } else if (nodeInfo instanceof Ext.data.Model) {
81847             return this.getNodeByRecord(nodeInfo);
81848         }
81849         return nodeInfo;
81850     },
81851     
81852     /**
81853      * @private
81854      */
81855     getNodeByRecord: function(record) {
81856         var ns = this.all.elements,
81857             ln = ns.length,
81858             i = 0;
81859         
81860         for (; i < ln; i++) {
81861             if (ns[i].viewRecordId === record.internalId) {
81862                 return ns[i];
81863             }
81864         }
81865         
81866         return null;
81867     },
81868     
81869     /**
81870      * Gets a range nodes.
81871      * @param {Number} start (optional) The index of the first node in the range
81872      * @param {Number} end (optional) The index of the last node in the range
81873      * @return {Array} An array of nodes
81874      */
81875     getNodes: function(start, end) {
81876         var ns = this.all.elements,
81877             nodes = [],
81878             i;
81879
81880         start = start || 0;
81881         end = !Ext.isDefined(end) ? Math.max(ns.length - 1, 0) : end;
81882         if (start <= end) {
81883             for (i = start; i <= end && ns[i]; i++) {
81884                 nodes.push(ns[i]);
81885             }
81886         } else {
81887             for (i = start; i >= end && ns[i]; i--) {
81888                 nodes.push(ns[i]);
81889             }
81890         }
81891         return nodes;
81892     },
81893
81894     /**
81895      * Finds the index of the passed node.
81896      * @param {HTMLElement/String/Number/Record} nodeInfo An HTMLElement template node, index of a template node, the id of a template node
81897      * or a record associated with a node.
81898      * @return {Number} The index of the node or -1
81899      */
81900     indexOf: function(node) {
81901         node = this.getNode(node);
81902         if (Ext.isNumber(node.viewIndex)) {
81903             return node.viewIndex;
81904         }
81905         return this.all.indexOf(node);
81906     },
81907
81908     onDestroy : function() {
81909         var me = this;
81910         
81911         me.all.clear();
81912         me.callParent();
81913         me.bindStore(null);
81914         me.selModel.destroy();
81915     },
81916
81917     // invoked by the selection model to maintain visual UI cues
81918     onItemSelect: function(record) {
81919         var node = this.getNode(record);
81920         Ext.fly(node).addCls(this.selectedItemCls);
81921     },
81922
81923     // invoked by the selection model to maintain visual UI cues
81924     onItemDeselect: function(record) {
81925         var node = this.getNode(record);
81926         Ext.fly(node).removeCls(this.selectedItemCls);
81927     },
81928     
81929     getItemSelector: function() {
81930         return this.itemSelector;
81931     }
81932 }, function() {
81933     // all of this information is available directly
81934     // from the SelectionModel itself, the only added methods
81935     // to DataView regarding selection will perform some transformation/lookup
81936     // between HTMLElement/Nodes to records and vice versa.
81937     Ext.deprecate('extjs', '4.0', function() {
81938         Ext.view.AbstractView.override({
81939             /**
81940              * @cfg {Boolean} multiSelect
81941              * True to allow selection of more than one item at a time, false to allow selection of only a single item
81942              * at a time or no selection at all, depending on the value of {@link #singleSelect} (defaults to false).
81943              */
81944             /**
81945              * @cfg {Boolean} singleSelect
81946              * True to allow selection of exactly one item at a time, false to allow no selection at all (defaults to false).
81947              * Note that if {@link #multiSelect} = true, this value will be ignored.
81948              */
81949             /**
81950              * @cfg {Boolean} simpleSelect
81951              * True to enable multiselection by clicking on multiple items without requiring the user to hold Shift or Ctrl,
81952              * false to force the user to hold Ctrl or Shift to select more than on item (defaults to false).
81953              */
81954             
81955             /**
81956              * Gets the number of selected nodes.
81957              * @return {Number} The node count
81958              */
81959             getSelectionCount : function(){
81960                 console.warn("DataView: getSelectionCount will be removed, please interact with the Ext.selection.DataViewModel");
81961                 return this.selModel.getSelection().length;
81962             },
81963         
81964             /**
81965              * Gets an array of the selected records
81966              * @return {Array} An array of {@link Ext.data.Model} objects
81967              */
81968             getSelectedRecords : function(){
81969                 console.warn("DataView: getSelectedRecords will be removed, please interact with the Ext.selection.DataViewModel");
81970                 return this.selModel.getSelection();
81971             },
81972     
81973             select: function(records, keepExisting, supressEvents) {
81974                 console.warn("DataView: select will be removed, please access select through a DataView's SelectionModel, ie: view.getSelectionModel().select()");
81975                 var sm = this.getSelectionModel();
81976                 return sm.select.apply(sm, arguments);
81977             },
81978             
81979             clearSelections: function() {
81980                 console.warn("DataView: clearSelections will be removed, please access deselectAll through DataView's SelectionModel, ie: view.getSelectionModel().deselectAll()");
81981                 var sm = this.getSelectionModel();
81982                 return sm.deselectAll();
81983             }
81984         });    
81985     });
81986 });
81987
81988 /**
81989  * @class Ext.Action
81990  * <p>An Action is a piece of reusable functionality that can be abstracted out of any particular component so that it
81991  * can be usefully shared among multiple components.  Actions let you share handlers, configuration options and UI
81992  * updates across any components that support the Action interface (primarily {@link Ext.toolbar.Toolbar}, {@link Ext.button.Button}
81993  * and {@link Ext.menu.Menu} components).</p>
81994  * <p>Use a single Action instance as the config object for any number of UI Components which share the same configuration. The
81995  * Action not only supplies the configuration, but allows all Components based upon it to have a common set of methods
81996  * called at once through a single call to the Action.</p>
81997  * <p>Any Component that is to be configured with an Action must also support
81998  * the following methods:<ul>
81999  * <li><code>setText(string)</code></li>
82000  * <li><code>setIconCls(string)</code></li>
82001  * <li><code>setDisabled(boolean)</code></li>
82002  * <li><code>setVisible(boolean)</code></li>
82003  * <li><code>setHandler(function)</code></li></ul>.</p>
82004  * <p>This allows the Action to control its associated Components.</p>
82005  * Example usage:<br>
82006  * <pre><code>
82007 // Define the shared Action.  Each Component below will have the same
82008 // display text and icon, and will display the same message on click.
82009 var action = new Ext.Action({
82010     {@link #text}: 'Do something',
82011     {@link #handler}: function(){
82012         Ext.Msg.alert('Click', 'You did something.');
82013     },
82014     {@link #iconCls}: 'do-something',
82015     {@link #itemId}: 'myAction'
82016 });
82017
82018 var panel = new Ext.panel.Panel({
82019     title: 'Actions',
82020     width: 500,
82021     height: 300,
82022     tbar: [
82023         // Add the Action directly to a toolbar as a menu button
82024         action,
82025         {
82026             text: 'Action Menu',
82027             // Add the Action to a menu as a text item
82028             menu: [action]
82029         }
82030     ],
82031     items: [
82032         // Add the Action to the panel body as a standard button
82033         new Ext.button.Button(action)
82034     ],
82035     renderTo: Ext.getBody()
82036 });
82037
82038 // Change the text for all components using the Action
82039 action.setText('Something else');
82040
82041 // Reference an Action through a container using the itemId
82042 var btn = panel.getComponent('myAction');
82043 var aRef = btn.baseAction;
82044 aRef.setText('New text');
82045 </code></pre>
82046  * @constructor
82047  * @param {Object} config The configuration options
82048  */
82049 Ext.define('Ext.Action', {
82050
82051     /* Begin Definitions */
82052
82053     /* End Definitions */
82054
82055     /**
82056      * @cfg {String} text The text to set for all components configured by this Action (defaults to '').
82057      */
82058     /**
82059      * @cfg {String} iconCls
82060      * The CSS class selector that specifies a background image to be used as the header icon for
82061      * all components configured by this Action (defaults to '').
82062      * <p>An example of specifying a custom icon class would be something like:
82063      * </p><pre><code>
82064 // specify the property in the config for the class:
82065      ...
82066      iconCls: 'do-something'
82067
82068 // css class that specifies background image to be used as the icon image:
82069 .do-something { background-image: url(../images/my-icon.gif) 0 6px no-repeat !important; }
82070 </code></pre>
82071      */
82072     /**
82073      * @cfg {Boolean} disabled True to disable all components configured by this Action, false to enable them (defaults to false).
82074      */
82075     /**
82076      * @cfg {Boolean} hidden True to hide all components configured by this Action, false to show them (defaults to false).
82077      */
82078     /**
82079      * @cfg {Function} handler The function that will be invoked by each component tied to this Action
82080      * when the component's primary event is triggered (defaults to undefined).
82081      */
82082     /**
82083      * @cfg {String} itemId
82084      * See {@link Ext.Component}.{@link Ext.Component#itemId itemId}.
82085      */
82086     /**
82087      * @cfg {Object} scope The scope (<code><b>this</b></code> reference) in which the
82088      * <code>{@link #handler}</code> is executed. Defaults to the browser window.
82089      */
82090
82091     constructor : function(config){
82092         this.initialConfig = config;
82093         this.itemId = config.itemId = (config.itemId || config.id || Ext.id());
82094         this.items = [];
82095     },
82096
82097     // private
82098     isAction : true,
82099
82100     /**
82101      * Sets the text to be displayed by all components configured by this Action.
82102      * @param {String} text The text to display
82103      */
82104     setText : function(text){
82105         this.initialConfig.text = text;
82106         this.callEach('setText', [text]);
82107     },
82108
82109     /**
82110      * Gets the text currently displayed by all components configured by this Action.
82111      */
82112     getText : function(){
82113         return this.initialConfig.text;
82114     },
82115
82116     /**
82117      * Sets the icon CSS class for all components configured by this Action.  The class should supply
82118      * a background image that will be used as the icon image.
82119      * @param {String} cls The CSS class supplying the icon image
82120      */
82121     setIconCls : function(cls){
82122         this.initialConfig.iconCls = cls;
82123         this.callEach('setIconCls', [cls]);
82124     },
82125
82126     /**
82127      * Gets the icon CSS class currently used by all components configured by this Action.
82128      */
82129     getIconCls : function(){
82130         return this.initialConfig.iconCls;
82131     },
82132
82133     /**
82134      * Sets the disabled state of all components configured by this Action.  Shortcut method
82135      * for {@link #enable} and {@link #disable}.
82136      * @param {Boolean} disabled True to disable the component, false to enable it
82137      */
82138     setDisabled : function(v){
82139         this.initialConfig.disabled = v;
82140         this.callEach('setDisabled', [v]);
82141     },
82142
82143     /**
82144      * Enables all components configured by this Action.
82145      */
82146     enable : function(){
82147         this.setDisabled(false);
82148     },
82149
82150     /**
82151      * Disables all components configured by this Action.
82152      */
82153     disable : function(){
82154         this.setDisabled(true);
82155     },
82156
82157     /**
82158      * Returns true if the components using this Action are currently disabled, else returns false.  
82159      */
82160     isDisabled : function(){
82161         return this.initialConfig.disabled;
82162     },
82163
82164     /**
82165      * Sets the hidden state of all components configured by this Action.  Shortcut method
82166      * for <code>{@link #hide}</code> and <code>{@link #show}</code>.
82167      * @param {Boolean} hidden True to hide the component, false to show it
82168      */
82169     setHidden : function(v){
82170         this.initialConfig.hidden = v;
82171         this.callEach('setVisible', [!v]);
82172     },
82173
82174     /**
82175      * Shows all components configured by this Action.
82176      */
82177     show : function(){
82178         this.setHidden(false);
82179     },
82180
82181     /**
82182      * Hides all components configured by this Action.
82183      */
82184     hide : function(){
82185         this.setHidden(true);
82186     },
82187
82188     /**
82189      * Returns true if the components configured by this Action are currently hidden, else returns false.
82190      */
82191     isHidden : function(){
82192         return this.initialConfig.hidden;
82193     },
82194
82195     /**
82196      * Sets the function that will be called by each Component using this action when its primary event is triggered.
82197      * @param {Function} fn The function that will be invoked by the action's components.  The function
82198      * will be called with no arguments.
82199      * @param {Object} scope The scope (<code>this</code> reference) in which the function is executed. Defaults to the Component firing the event.
82200      */
82201     setHandler : function(fn, scope){
82202         this.initialConfig.handler = fn;
82203         this.initialConfig.scope = scope;
82204         this.callEach('setHandler', [fn, scope]);
82205     },
82206
82207     /**
82208      * Executes the specified function once for each Component currently tied to this Action.  The function passed
82209      * in should accept a single argument that will be an object that supports the basic Action config/method interface.
82210      * @param {Function} fn The function to execute for each component
82211      * @param {Object} scope The scope (<code>this</code> reference) in which the function is executed.  Defaults to the Component.
82212      */
82213     each : function(fn, scope){
82214         Ext.each(this.items, fn, scope);
82215     },
82216
82217     // private
82218     callEach : function(fnName, args){
82219         var cs = this.items;
82220         for(var i = 0, len = cs.length; i < len; i++){
82221             cs[i][fnName].apply(cs[i], args);
82222         }
82223     },
82224
82225     // private
82226     addComponent : function(comp){
82227         this.items.push(comp);
82228         comp.on('destroy', this.removeComponent, this);
82229     },
82230
82231     // private
82232     removeComponent : function(comp){
82233         this.items.remove(comp);
82234     },
82235
82236     /**
82237      * Executes this Action manually using the handler function specified in the original config object
82238      * or the handler function set with <code>{@link #setHandler}</code>.  Any arguments passed to this
82239      * function will be passed on to the handler function.
82240      * @param {Mixed} arg1 (optional) Variable number of arguments passed to the handler function
82241      * @param {Mixed} arg2 (optional)
82242      * @param {Mixed} etc... (optional)
82243      */
82244     execute : function(){
82245         this.initialConfig.handler.apply(this.initialConfig.scope || Ext.global, arguments);
82246     }
82247 });
82248
82249 /**
82250  * Component layout for editors
82251  * @class Ext.layout.component.Editor
82252  * @extends Ext.layout.component.Component
82253  * @private
82254  */
82255 Ext.define('Ext.layout.component.Editor', {
82256
82257     /* Begin Definitions */
82258
82259     alias: ['layout.editor'],
82260
82261     extend: 'Ext.layout.component.Component',
82262
82263     /* End Definitions */
82264
82265     onLayout: function(width, height) {
82266         var me = this,
82267             owner = me.owner,
82268             autoSize = owner.autoSize;
82269             
82270         if (autoSize === true) {
82271             autoSize = {
82272                 width: 'field',
82273                 height: 'field'    
82274             };
82275         }
82276         
82277         if (autoSize) {
82278             width = me.getDimension(owner, autoSize.width, 'Width', width);
82279             height = me.getDimension(owner, autoSize.height, 'Height', height);
82280         }
82281         me.setTargetSize(width, height);
82282         owner.field.setSize(width, height);
82283     },
82284     
82285     getDimension: function(owner, type, dimension, actual){
82286         var method = 'get' + dimension;
82287         switch (type) {
82288             case 'boundEl':
82289                 return owner.boundEl[method]();
82290             case 'field':
82291                 return owner.field[method]();
82292             default:
82293                 return actual;
82294         }
82295     }
82296 });
82297 /**
82298  * @class Ext.Editor
82299  * @extends Ext.Component
82300  *
82301  * <p>
82302  * The Editor class is used to provide inline editing for elements on the page. The editor
82303  * is backed by a {@link Ext.form.field.Field} that will be displayed to edit the underlying content.
82304  * The editor is a floating Component, when the editor is shown it is automatically aligned to
82305  * display over the top of the bound element it is editing. The Editor contains several options
82306  * for how to handle key presses:
82307  * <ul>
82308  * <li>{@link #completeOnEnter}</li>
82309  * <li>{@link #cancelOnEsc}</li>
82310  * <li>{@link #swallowKeys}</li>
82311  * </ul>
82312  * It also has options for how to use the value once the editor has been activated:
82313  * <ul>
82314  * <li>{@link #revertInvalid}</li>
82315  * <li>{@link #ignoreNoChange}</li>
82316  * <li>{@link #updateEl}</li>
82317  * </ul>
82318  * Sample usage:
82319  * </p>
82320  * <pre><code>
82321 var editor = new Ext.Editor({
82322     updateEl: true, // update the innerHTML of the bound element when editing completes
82323     field: {
82324         xtype: 'textfield'
82325     }
82326 });
82327 var el = Ext.get('my-text'); // The element to 'edit'
82328 editor.startEdit(el); // The value of the field will be taken as the innerHTML of the element.
82329  * </code></pre>
82330  * {@img Ext.Editor/Ext.Editor.png Ext.Editor component}
82331  *
82332  * @constructor
82333  * Create a new Editor
82334  * @param {Object} config The config object
82335  * @xtype editor
82336  */
82337 Ext.define('Ext.Editor', {
82338
82339     /* Begin Definitions */
82340
82341     extend: 'Ext.Component',
82342
82343     alias: 'widget.editor',
82344
82345     requires: ['Ext.layout.component.Editor'],
82346
82347     /* End Definitions */
82348
82349    componentLayout: 'editor',
82350
82351     /**
82352     * @cfg {Ext.form.field.Field} field
82353     * The Field object (or descendant) or config object for field
82354     */
82355
82356     /**
82357      * @cfg {Boolean} allowBlur
82358      * True to {@link #completeEdit complete the editing process} if in edit mode when the
82359      * field is blurred. Defaults to <tt>true</tt>.
82360      */
82361     allowBlur: true,
82362
82363     /**
82364      * @cfg {Boolean/Object} autoSize
82365      * True for the editor to automatically adopt the size of the underlying field. Otherwise, an object
82366      * can be passed to indicate where to get each dimension. The available properties are 'boundEl' and
82367      * 'field'. If a dimension is not specified, it will use the underlying height/width specified on
82368      * the editor object.
82369      * Examples:
82370      * <pre><code>
82371 autoSize: true // The editor will be sized to the height/width of the field
82372
82373 height: 21,
82374 autoSize: {
82375     width: 'boundEl' // The width will be determined by the width of the boundEl, the height from the editor (21)
82376 }
82377
82378 autoSize: {
82379     width: 'field', // Width from the field
82380     height: 'boundEl' // Height from the boundEl
82381 }
82382      * </pre></code>
82383      */
82384
82385     /**
82386      * @cfg {Boolean} revertInvalid
82387      * True to automatically revert the field value and cancel the edit when the user completes an edit and the field
82388      * validation fails (defaults to true)
82389      */
82390     revertInvalid: true,
82391
82392     /**
82393      * @cfg {Boolean} ignoreNoChange
82394      * True to skip the edit completion process (no save, no events fired) if the user completes an edit and
82395      * the value has not changed (defaults to false).  Applies only to string values - edits for other data types
82396      * will never be ignored.
82397      */
82398
82399     /**
82400      * @cfg {Boolean} hideEl
82401      * False to keep the bound element visible while the editor is displayed (defaults to true)
82402      */
82403
82404     /**
82405      * @cfg {Mixed} value
82406      * The data value of the underlying field (defaults to "")
82407      */
82408     value : '',
82409
82410     /**
82411      * @cfg {String} alignment
82412      * The position to align to (see {@link Ext.core.Element#alignTo} for more details, defaults to "c-c?").
82413      */
82414     alignment: 'c-c?',
82415
82416     /**
82417      * @cfg {Array} offsets
82418      * The offsets to use when aligning (see {@link Ext.core.Element#alignTo} for more details. Defaults to <tt>[0, 0]</tt>.
82419      */
82420     offsets: [0, 0],
82421
82422     /**
82423      * @cfg {Boolean/String} shadow "sides" for sides/bottom only, "frame" for 4-way shadow, and "drop"
82424      * for bottom-right shadow (defaults to "frame")
82425      */
82426     shadow : 'frame',
82427
82428     /**
82429      * @cfg {Boolean} constrain True to constrain the editor to the viewport
82430      */
82431     constrain : false,
82432
82433     /**
82434      * @cfg {Boolean} swallowKeys Handle the keydown/keypress events so they don't propagate (defaults to true)
82435      */
82436     swallowKeys : true,
82437
82438     /**
82439      * @cfg {Boolean} completeOnEnter True to complete the edit when the enter key is pressed. Defaults to <tt>true</tt>.
82440      */
82441     completeOnEnter : true,
82442
82443     /**
82444      * @cfg {Boolean} cancelOnEsc True to cancel the edit when the escape key is pressed. Defaults to <tt>true</tt>.
82445      */
82446     cancelOnEsc : true,
82447
82448     /**
82449      * @cfg {Boolean} updateEl True to update the innerHTML of the bound element when the update completes (defaults to false)
82450      */
82451     updateEl : false,
82452
82453     /**
82454      * @cfg {Mixed} parentEl An element to render to. Defaults to the <tt>document.body</tt>.
82455      */
82456
82457     // private overrides
82458     hidden: true,
82459     baseCls: Ext.baseCSSPrefix + 'editor',
82460
82461     initComponent : function() {
82462         var me = this,
82463             field = me.field = Ext.ComponentManager.create(me.field, 'textfield');
82464
82465         Ext.apply(field, {
82466             inEditor: true,
82467             msgTarget: field.msgTarget == 'title' ? 'title' :  'qtip'
82468         });
82469         me.mon(field, {
82470             scope: me,
82471             blur: {
82472                 fn: me.onBlur,
82473                 // slight delay to avoid race condition with startEdits (e.g. grid view refresh)
82474                 delay: 1
82475             },
82476             specialkey: me.onSpecialKey
82477         });
82478
82479         if (field.grow) {
82480             me.mon(field, 'autosize', me.onAutoSize,  me, {delay: 1});
82481         }
82482         me.floating = {
82483             constrain: me.constrain
82484         };
82485
82486         me.callParent(arguments);
82487
82488         me.addEvents(
82489             /**
82490              * @event beforestartedit
82491              * Fires when editing is initiated, but before the value changes.  Editing can be canceled by returning
82492              * false from the handler of this event.
82493              * @param {Ext.Editor} this
82494              * @param {Ext.core.Element} boundEl The underlying element bound to this editor
82495              * @param {Mixed} value The field value being set
82496              */
82497             'beforestartedit',
82498             /**
82499              * @event startedit
82500              * Fires when this editor is displayed
82501              * @param {Ext.Editor} this
82502              * @param {Ext.core.Element} boundEl The underlying element bound to this editor
82503              * @param {Mixed} value The starting field value
82504              */
82505             'startedit',
82506             /**
82507              * @event beforecomplete
82508              * Fires after a change has been made to the field, but before the change is reflected in the underlying
82509              * field.  Saving the change to the field can be canceled by returning false from the handler of this event.
82510              * Note that if the value has not changed and ignoreNoChange = true, the editing will still end but this
82511              * event will not fire since no edit actually occurred.
82512              * @param {Editor} this
82513              * @param {Mixed} value The current field value
82514              * @param {Mixed} startValue The original field value
82515              */
82516             'beforecomplete',
82517             /**
82518              * @event complete
82519              * Fires after editing is complete and any changed value has been written to the underlying field.
82520              * @param {Ext.Editor} this
82521              * @param {Mixed} value The current field value
82522              * @param {Mixed} startValue The original field value
82523              */
82524             'complete',
82525             /**
82526              * @event canceledit
82527              * Fires after editing has been canceled and the editor's value has been reset.
82528              * @param {Ext.Editor} this
82529              * @param {Mixed} value The user-entered field value that was discarded
82530              * @param {Mixed} startValue The original field value that was set back into the editor after cancel
82531              */
82532             'canceledit',
82533             /**
82534              * @event specialkey
82535              * Fires when any key related to navigation (arrows, tab, enter, esc, etc.) is pressed.  You can check
82536              * {@link Ext.EventObject#getKey} to determine which key was pressed.
82537              * @param {Ext.Editor} this
82538              * @param {Ext.form.field.Field} The field attached to this editor
82539              * @param {Ext.EventObject} event The event object
82540              */
82541             'specialkey'
82542         );
82543     },
82544
82545     // private
82546     onAutoSize: function(){
82547         this.doComponentLayout();
82548     },
82549
82550     // private
82551     onRender : function(ct, position) {
82552         var me = this,
82553             field = me.field;
82554
82555         me.callParent(arguments);
82556
82557         field.render(me.el);
82558         //field.hide();
82559         // Ensure the field doesn't get submitted as part of any form
82560         field.inputEl.dom.name = '';
82561         if (me.swallowKeys) {
82562             field.inputEl.swallowEvent([
82563                 'keypress', // *** Opera
82564                 'keydown'   // *** all other browsers
82565             ]);
82566         }
82567     },
82568
82569     // private
82570     onSpecialKey : function(field, event) {
82571         var me = this,
82572             key = event.getKey(),
82573             complete = me.completeOnEnter && key == event.ENTER,
82574             cancel = me.cancelOnEsc && key == event.ESC;
82575
82576         if (complete || cancel) {
82577             event.stopEvent();
82578             // Must defer this slightly to prevent exiting edit mode before the field's own
82579             // key nav can handle the enter key, e.g. selecting an item in a combobox list
82580             Ext.defer(function() {
82581                 if (complete) {
82582                     me.completeEdit();
82583                 } else {
82584                     me.cancelEdit();
82585                 }
82586                 if (field.triggerBlur) {
82587                     field.triggerBlur();
82588                 }
82589             }, 10);
82590         }
82591
82592         this.fireEvent('specialkey', this, field, event);
82593     },
82594
82595     /**
82596      * Starts the editing process and shows the editor.
82597      * @param {Mixed} el The element to edit
82598      * @param {String} value (optional) A value to initialize the editor with. If a value is not provided, it defaults
82599       * to the innerHTML of el.
82600      */
82601     startEdit : function(el, value) {
82602         var me = this,
82603             field = me.field;
82604
82605         me.completeEdit();
82606         me.boundEl = Ext.get(el);
82607         value = Ext.isDefined(value) ? value : me.boundEl.dom.innerHTML;
82608
82609         if (!me.rendered) {
82610             me.render(me.parentEl || document.body);
82611         }
82612
82613         if (me.fireEvent('beforestartedit', me, me.boundEl, value) !== false) {
82614             me.startValue = value;
82615             me.show();
82616             field.reset();
82617             field.setValue(value);
82618             me.realign(true);
82619             field.focus(false, 10);
82620             if (field.autoSize) {
82621                 field.autoSize();
82622             }
82623             me.editing = true;
82624         }
82625     },
82626
82627     /**
82628      * Realigns the editor to the bound field based on the current alignment config value.
82629      * @param {Boolean} autoSize (optional) True to size the field to the dimensions of the bound element.
82630      */
82631     realign : function(autoSize) {
82632         var me = this;
82633         if (autoSize === true) {
82634             me.doComponentLayout();
82635         }
82636         me.alignTo(me.boundEl, me.alignment, me.offsets);
82637     },
82638
82639     /**
82640      * Ends the editing process, persists the changed value to the underlying field, and hides the editor.
82641      * @param {Boolean} remainVisible Override the default behavior and keep the editor visible after edit (defaults to false)
82642      */
82643     completeEdit : function(remainVisible) {
82644         var me = this,
82645             field = me.field,
82646             value;
82647
82648         if (!me.editing) {
82649             return;
82650         }
82651
82652         // Assert combo values first
82653         if (field.assertValue) {
82654             field.assertValue();
82655         }
82656
82657         value = me.getValue();
82658         if (!field.isValid()) {
82659             if (me.revertInvalid !== false) {
82660                 me.cancelEdit(remainVisible);
82661             }
82662             return;
82663         }
82664
82665         if (String(value) === String(me.startValue) && me.ignoreNoChange) {
82666             me.hideEdit(remainVisible);
82667             return;
82668         }
82669
82670         if (me.fireEvent('beforecomplete', me, value, me.startValue) !== false) {
82671             // Grab the value again, may have changed in beforecomplete
82672             value = me.getValue();
82673             if (me.updateEl && me.boundEl) {
82674                 me.boundEl.update(value);
82675             }
82676             me.hideEdit(remainVisible);
82677             me.fireEvent('complete', me, value, me.startValue);
82678         }
82679     },
82680
82681     // private
82682     onShow : function() {
82683         var me = this;
82684
82685         me.callParent(arguments);
82686         if (me.hideEl !== false) {
82687             me.boundEl.hide();
82688         }
82689         me.fireEvent("startedit", me.boundEl, me.startValue);
82690     },
82691
82692     /**
82693      * Cancels the editing process and hides the editor without persisting any changes.  The field value will be
82694      * reverted to the original starting value.
82695      * @param {Boolean} remainVisible Override the default behavior and keep the editor visible after
82696      * cancel (defaults to false)
82697      */
82698     cancelEdit : function(remainVisible) {
82699         var me = this,
82700             startValue = me.startValue,
82701             value;
82702
82703         if (me.editing) {
82704             value = me.getValue();
82705             me.setValue(startValue);
82706             me.hideEdit(remainVisible);
82707             me.fireEvent('canceledit', me, value, startValue);
82708         }
82709     },
82710
82711     // private
82712     hideEdit: function(remainVisible) {
82713         if (remainVisible !== true) {
82714             this.editing = false;
82715             this.hide();
82716         }
82717     },
82718
82719     // private
82720     onBlur : function() {
82721         var me = this;
82722
82723         // selectSameEditor flag allows the same editor to be started without onBlur firing on itself
82724         if(me.allowBlur === true && me.editing && me.selectSameEditor !== true) {
82725             me.completeEdit();
82726         }
82727     },
82728
82729     // private
82730     onHide : function() {
82731         var me = this,
82732             field = me.field;
82733
82734         if (me.editing) {
82735             me.completeEdit();
82736             return;
82737         }
82738         field.blur();
82739         if (field.collapse) {
82740             field.collapse();
82741         }
82742
82743         //field.hide();
82744         if (me.hideEl !== false) {
82745             me.boundEl.show();
82746         }
82747         me.callParent(arguments);
82748     },
82749
82750     /**
82751      * Sets the data value of the editor
82752      * @param {Mixed} value Any valid value supported by the underlying field
82753      */
82754     setValue : function(value) {
82755         this.field.setValue(value);
82756     },
82757
82758     /**
82759      * Gets the data value of the editor
82760      * @return {Mixed} The data value
82761      */
82762     getValue : function() {
82763         return this.field.getValue();
82764     },
82765
82766     beforeDestroy : function() {
82767         var me = this;
82768
82769         Ext.destroy(me.field);
82770         delete me.field;
82771         delete me.parentEl;
82772         delete me.boundEl;
82773
82774         me.callParent(arguments);
82775     }
82776 });
82777 /**
82778  * @class Ext.Img
82779  * @extends Ext.Component
82780  *
82781  * Simple helper class for easily creating image components. This simply renders an image tag to the DOM
82782  * with the configured src.
82783  *
82784  * {@img Ext.Img/Ext.Img.png Ext.Img component}
82785  *
82786  * ## Example usage: 
82787  *
82788  *     var changingImage = Ext.create('Ext.Img', {
82789  *         src: 'http://www.sencha.com/img/20110215-feat-html5.png',
82790  *         renderTo: Ext.getBody()
82791  *     });
82792  *      
82793  *     // change the src of the image programmatically
82794  *     changingImage.setSrc('http://www.sencha.com/img/20110215-feat-perf.png');
82795 */
82796 Ext.define('Ext.Img', {
82797     extend: 'Ext.Component',
82798     alias: ['widget.image', 'widget.imagecomponent'],
82799     /** @cfg {String} src The image src */
82800     src: '',
82801
82802     getElConfig: function() {
82803         return {
82804             tag: 'img',
82805             src: this.src
82806         };
82807     },
82808     
82809     /**
82810      * Updates the {@link #src} of the image
82811      */
82812     setSrc: function(src) {
82813         var me = this,
82814             img = me.el;
82815         me.src = src;
82816         if (img) {
82817             img.dom.src = src;
82818         }
82819     }
82820 });
82821
82822 /**
82823  * @class Ext.Layer
82824  * @extends Ext.core.Element
82825  * An extended {@link Ext.core.Element} object that supports a shadow and shim, constrain to viewport and
82826  * automatic maintaining of shadow/shim positions.
82827  * @cfg {Boolean} shim False to disable the iframe shim in browsers which need one (defaults to true)
82828  * @cfg {String/Boolean} shadow True to automatically create an {@link Ext.Shadow}, or a string indicating the
82829  * shadow's display {@link Ext.Shadow#mode}. False to disable the shadow. (defaults to false)
82830  * @cfg {Object} dh DomHelper object config to create element with (defaults to {tag: 'div', cls: 'x-layer'}).
82831  * @cfg {Boolean} constrain False to disable constrain to viewport (defaults to true)
82832  * @cfg {String} cls CSS class to add to the element
82833  * @cfg {Number} zindex Starting z-index (defaults to 11000)
82834  * @cfg {Number} shadowOffset Number of pixels to offset the shadow (defaults to 4)
82835  * @cfg {Boolean} useDisplay
82836  * Defaults to use css offsets to hide the Layer. Specify <tt>true</tt>
82837  * to use css style <tt>'display:none;'</tt> to hide the Layer.
82838  * @cfg {String} visibilityCls The CSS class name to add in order to hide this Layer if this layer
82839  * is configured with <code>{@link #hideMode}: 'asclass'</code>
82840  * @cfg {String} hideMode
82841  * A String which specifies how this Layer will be hidden.
82842  * Values may be<div class="mdetail-params"><ul>
82843  * <li><code>'display'</code> : The Component will be hidden using the <code>display: none</code> style.</li>
82844  * <li><code>'visibility'</code> : The Component will be hidden using the <code>visibility: hidden</code> style.</li>
82845  * <li><code>'offsets'</code> : The Component will be hidden by absolutely positioning it out of the visible area of the document. This
82846  * is useful when a hidden Component must maintain measurable dimensions. Hiding using <code>display</code> results
82847  * in a Component having zero dimensions.</li></ul></div>
82848  * @constructor
82849  * @param {Object} config An object with config options.
82850  * @param {String/HTMLElement} existingEl (optional) Uses an existing DOM element. If the element is not found it creates it.
82851  */
82852 Ext.define('Ext.Layer', {
82853     uses: ['Ext.Shadow'],
82854
82855     // shims are shared among layer to keep from having 100 iframes
82856     statics: {
82857         shims: []
82858     },
82859
82860     extend: 'Ext.core.Element',
82861
82862     constructor: function(config, existingEl) {
82863         config = config || {};
82864         var me = this,
82865             dh = Ext.core.DomHelper,
82866             cp = config.parentEl,
82867             pel = cp ? Ext.getDom(cp) : document.body,
82868         hm = config.hideMode;
82869
82870         if (existingEl) {
82871             me.dom = Ext.getDom(existingEl);
82872         }
82873         if (!me.dom) {
82874             me.dom = dh.append(pel, config.dh || {
82875                 tag: 'div',
82876                 cls: Ext.baseCSSPrefix + 'layer'
82877             });
82878         } else {
82879             me.addCls(Ext.baseCSSPrefix + 'layer');
82880             if (!me.dom.parentNode) {
82881                 pel.appendChild(me.dom);
82882             }
82883         }
82884
82885         if (config.cls) {
82886             me.addCls(config.cls);
82887         }
82888         me.constrain = config.constrain !== false;
82889
82890         // Allow Components to pass their hide mode down to the Layer if they are floating.
82891         // Otherwise, allow useDisplay to override the default hiding method which is visibility.
82892         // TODO: Have ExtJS's Element implement visibilityMode by using classes as in Mobile.
82893         if (hm) {
82894             me.setVisibilityMode(Ext.core.Element[hm.toUpperCase()]);
82895             if (me.visibilityMode == Ext.core.Element.ASCLASS) {
82896                 me.visibilityCls = config.visibilityCls;
82897             }
82898         } else if (config.useDisplay) {
82899             me.setVisibilityMode(Ext.core.Element.DISPLAY);
82900         } else {
82901             me.setVisibilityMode(Ext.core.Element.VISIBILITY);
82902         }
82903
82904         if (config.id) {
82905             me.id = me.dom.id = config.id;
82906         } else {
82907             me.id = Ext.id(me.dom);
82908         }
82909         me.position('absolute');
82910         if (config.shadow) {
82911             me.shadowOffset = config.shadowOffset || 4;
82912             me.shadow = Ext.create('Ext.Shadow', {
82913                 offset: me.shadowOffset,
82914                 mode: config.shadow
82915             });
82916             me.disableShadow();
82917         } else {
82918             me.shadowOffset = 0;
82919         }
82920         me.useShim = config.shim !== false && Ext.useShims;
82921         if (config.hidden === true) {
82922             me.hide();
82923         } else {
82924             this.show();
82925         }
82926     },
82927
82928     getZIndex: function() {
82929         return parseInt((this.getShim() || this).getStyle('z-index'), 10);
82930     },
82931
82932     getShim: function() {
82933         var me = this,
82934             shim, pn;
82935
82936         if (!me.useShim) {
82937             return null;
82938         }
82939         if (!me.shim) {
82940             shim = me.self.shims.shift();
82941             if (!shim) {
82942                 shim = me.createShim();
82943                 shim.enableDisplayMode('block');
82944                 shim.hide();
82945             }
82946             pn = me.dom.parentNode;
82947             if (shim.dom.parentNode != pn) {
82948                 pn.insertBefore(shim.dom, me.dom);
82949             }
82950             me.shim = shim;
82951         }
82952         return me.shim;
82953     },
82954
82955     hideShim: function() {
82956         if (this.shim) {
82957             this.shim.setDisplayed(false);
82958             this.self.shims.push(this.shim);
82959             delete this.shim;
82960         }
82961     },
82962
82963     disableShadow: function() {
82964         if (this.shadow) {
82965             this.shadowDisabled = true;
82966             this.shadow.hide();
82967             this.lastShadowOffset = this.shadowOffset;
82968             this.shadowOffset = 0;
82969         }
82970     },
82971
82972     enableShadow: function(show) {
82973         if (this.shadow) {
82974             this.shadowDisabled = false;
82975             this.shadowOffset = this.lastShadowOffset;
82976             delete this.lastShadowOffset;
82977             if (show) {
82978                 this.sync(true);
82979             }
82980         }
82981     },
82982
82983     /**
82984      * @private
82985      * <p>Synchronize this Layer's associated elements, the shadow, and possibly the shim.</p>
82986      * <p>This code can execute repeatedly in milliseconds,
82987      * eg: dragging a Component configured liveDrag: true, or which has no ghost method
82988      * so code size was sacrificed for efficiency (e.g. no getBox/setBox, no XY calls)</p>
82989      * @param {Boolean} doShow Pass true to ensure that the shadow is shown.
82990      */
82991     sync: function(doShow) {
82992         var me = this,
82993             shadow = me.shadow,
82994             shadowPos, shimStyle, shadowSize;
82995
82996         if (!this.updating && this.isVisible() && (shadow || this.useShim)) {
82997             var shim = this.getShim(),
82998                 l = this.getLeft(true),
82999                 t = this.getTop(true),
83000                 w = this.getWidth(),
83001                 h = this.getHeight(),
83002                 shimIndex;
83003
83004             if (shadow && !this.shadowDisabled) {
83005                 if (doShow && !shadow.isVisible()) {
83006                     shadow.show(this);
83007                 } else {
83008                     shadow.realign(l, t, w, h);
83009                 }
83010                 if (shim) {
83011                     // TODO: Determine how the shims zIndex is above the layer zIndex at this point
83012                     shimIndex = shim.getStyle('z-index');
83013                     if (shimIndex > me.zindex) {
83014                         me.shim.setStyle('z-index', me.zindex - 2);
83015                     }
83016                     shim.show();
83017                     // fit the shim behind the shadow, so it is shimmed too
83018                     if (shadow.isVisible()) {
83019                         shadowPos = shadow.el.getXY();
83020                         shimStyle = shim.dom.style;
83021                         shadowSize = shadow.el.getSize();
83022                         shimStyle.left = (shadowPos[0]) + 'px';
83023                         shimStyle.top = (shadowPos[1]) + 'px';
83024                         shimStyle.width = (shadowSize.width) + 'px';
83025                         shimStyle.height = (shadowSize.height) + 'px';
83026                     } else {
83027                         shim.setSize(w, h);
83028                         shim.setLeftTop(l, t);
83029                     }
83030                 }
83031             } else if (shim) {
83032                 // TODO: Determine how the shims zIndex is above the layer zIndex at this point
83033                 shimIndex = shim.getStyle('z-index');
83034                 if (shimIndex > me.zindex) {
83035                     me.shim.setStyle('z-index', me.zindex - 2);
83036                 }
83037                 shim.show();
83038                 shim.setSize(w, h);
83039                 shim.setLeftTop(l, t);
83040             }
83041         }
83042         return this;
83043     },
83044
83045     remove: function() {
83046         this.hideUnders();
83047         this.callParent();
83048     },
83049
83050     // private
83051     beginUpdate: function() {
83052         this.updating = true;
83053     },
83054
83055     // private
83056     endUpdate: function() {
83057         this.updating = false;
83058         this.sync(true);
83059     },
83060
83061     // private
83062     hideUnders: function() {
83063         if (this.shadow) {
83064             this.shadow.hide();
83065         }
83066         this.hideShim();
83067     },
83068
83069     // private
83070     constrainXY: function() {
83071         if (this.constrain) {
83072             var vw = Ext.core.Element.getViewWidth(),
83073                 vh = Ext.core.Element.getViewHeight(),
83074                 s = Ext.getDoc().getScroll(),
83075                 xy = this.getXY(),
83076                 x = xy[0],
83077                 y = xy[1],
83078                 so = this.shadowOffset,
83079                 w = this.dom.offsetWidth + so,
83080                 h = this.dom.offsetHeight + so,
83081                 moved = false; // only move it if it needs it
83082             // first validate right/bottom
83083             if ((x + w) > vw + s.left) {
83084                 x = vw - w - so;
83085                 moved = true;
83086             }
83087             if ((y + h) > vh + s.top) {
83088                 y = vh - h - so;
83089                 moved = true;
83090             }
83091             // then make sure top/left isn't negative
83092             if (x < s.left) {
83093                 x = s.left;
83094                 moved = true;
83095             }
83096             if (y < s.top) {
83097                 y = s.top;
83098                 moved = true;
83099             }
83100             if (moved) {
83101                 Ext.Layer.superclass.setXY.call(this, [x, y]);
83102                 this.sync();
83103             }
83104         }
83105         return this;
83106     },
83107
83108     getConstrainOffset: function() {
83109         return this.shadowOffset;
83110     },
83111
83112     // overridden Element method
83113     setVisible: function(visible, animate, duration, callback, easing) {
83114         var me = this,
83115             cb;
83116
83117         // post operation processing
83118         cb = function() {
83119             if (visible) {
83120                 me.sync(true);
83121             }
83122             if (callback) {
83123                 callback();
83124             }
83125         };
83126
83127         // Hide shadow and shim if hiding
83128         if (!visible) {
83129             this.hideUnders(true);
83130         }
83131         this.callParent([visible, animate, duration, callback, easing]);
83132         if (!animate) {
83133             cb();
83134         }
83135         return this;
83136     },
83137
83138     // private
83139     beforeFx: function() {
83140         this.beforeAction();
83141         return this.callParent(arguments);
83142     },
83143
83144     // private
83145     afterFx: function() {
83146         this.callParent(arguments);
83147         this.sync(this.isVisible());
83148     },
83149
83150     // private
83151     beforeAction: function() {
83152         if (!this.updating && this.shadow) {
83153             this.shadow.hide();
83154         }
83155     },
83156
83157     // overridden Element method
83158     setLeft: function(left) {
83159         this.callParent(arguments);
83160         return this.sync();
83161     },
83162
83163     setTop: function(top) {
83164         this.callParent(arguments);
83165         return this.sync();
83166     },
83167
83168     setLeftTop: function(left, top) {
83169         this.callParent(arguments);
83170         return this.sync();
83171     },
83172
83173     setXY: function(xy, animate, duration, callback, easing) {
83174
83175         // Callback will restore shadow state and call the passed callback
83176         callback = this.createCB(callback);
83177
83178         this.fixDisplay();
83179         this.beforeAction();
83180         this.callParent([xy, animate, duration, callback, easing]);
83181         if (!animate) {
83182             callback();
83183         }
83184         return this;
83185     },
83186
83187     // private
83188     createCB: function(callback) {
83189         var me = this,
83190             showShadow = me.shadow && me.shadow.isVisible();
83191
83192         return function() {
83193             me.constrainXY();
83194             me.sync(showShadow);
83195             if (callback) {
83196                 callback();
83197             }
83198         };
83199     },
83200
83201     // overridden Element method
83202     setX: function(x, animate, duration, callback, easing) {
83203         this.setXY([x, this.getY()], animate, duration, callback, easing);
83204         return this;
83205     },
83206
83207     // overridden Element method
83208     setY: function(y, animate, duration, callback, easing) {
83209         this.setXY([this.getX(), y], animate, duration, callback, easing);
83210         return this;
83211     },
83212
83213     // overridden Element method
83214     setSize: function(w, h, animate, duration, callback, easing) {
83215         // Callback will restore shadow state and call the passed callback
83216         callback = this.createCB(callback);
83217
83218         this.beforeAction();
83219         this.callParent([w, h, animate, duration, callback, easing]);
83220         if (!animate) {
83221             callback();
83222         }
83223         return this;
83224     },
83225
83226     // overridden Element method
83227     setWidth: function(w, animate, duration, callback, easing) {
83228         // Callback will restore shadow state and call the passed callback
83229         callback = this.createCB(callback);
83230
83231         this.beforeAction();
83232         this.callParent([w, animate, duration, callback, easing]);
83233         if (!animate) {
83234             callback();
83235         }
83236         return this;
83237     },
83238
83239     // overridden Element method
83240     setHeight: function(h, animate, duration, callback, easing) {
83241         // Callback will restore shadow state and call the passed callback
83242         callback = this.createCB(callback);
83243
83244         this.beforeAction();
83245         this.callParent([h, animate, duration, callback, easing]);
83246         if (!animate) {
83247             callback();
83248         }
83249         return this;
83250     },
83251
83252     // overridden Element method
83253     setBounds: function(x, y, width, height, animate, duration, callback, easing) {
83254         // Callback will restore shadow state and call the passed callback
83255         callback = this.createCB(callback);
83256
83257         this.beforeAction();
83258         if (!animate) {
83259             Ext.Layer.superclass.setXY.call(this, [x, y]);
83260             Ext.Layer.superclass.setSize.call(this, width, height);
83261             callback();
83262         } else {
83263             this.callParent([x, y, width, height, animate, duration, callback, easing]);
83264         }
83265         return this;
83266     },
83267
83268     /**
83269      * <p>Sets the z-index of this layer and adjusts any shadow and shim z-indexes. The layer z-index is automatically
83270      * incremented depending upon the presence of a shim or a shadow in so that it always shows above those two associated elements.</p>
83271      * <p>Any shim, will be assigned the passed z-index. A shadow will be assigned the next highet z-index, and the Layer's
83272      * element will receive the highest  z-index.
83273      * @param {Number} zindex The new z-index to set
83274      * @return {this} The Layer
83275      */
83276     setZIndex: function(zindex) {
83277         this.zindex = zindex;
83278         if (this.getShim()) {
83279             this.shim.setStyle('z-index', zindex++);
83280         }
83281         if (this.shadow) {
83282             this.shadow.setZIndex(zindex++);
83283         }
83284         this.setStyle('z-index', zindex);
83285         return this;
83286     }
83287 });
83288
83289 /**
83290  * @class Ext.layout.component.ProgressBar
83291  * @extends Ext.layout.component.Component
83292  * @private
83293  */
83294
83295 Ext.define('Ext.layout.component.ProgressBar', {
83296
83297     /* Begin Definitions */
83298
83299     alias: ['layout.progressbar'],
83300
83301     extend: 'Ext.layout.component.Component',
83302
83303     /* End Definitions */
83304
83305     type: 'progressbar',
83306
83307     onLayout: function(width, height) {
83308         var me = this,
83309             owner = me.owner,
83310             textEl = owner.textEl;
83311         
83312         me.setElementSize(owner.el, width, height);
83313         textEl.setWidth(owner.el.getWidth(true));
83314         
83315         me.callParent([width, height]);
83316         
83317         owner.updateProgress(owner.value);
83318     }
83319 });
83320 /**
83321  * @class Ext.ProgressBar
83322  * @extends Ext.Component
83323  * <p>An updateable progress bar component.  The progress bar supports two different modes: manual and automatic.</p>
83324  * <p>In manual mode, you are responsible for showing, updating (via {@link #updateProgress}) and clearing the
83325  * progress bar as needed from your own code.  This method is most appropriate when you want to show progress
83326  * throughout an operation that has predictable points of interest at which you can update the control.</p>
83327  * <p>In automatic mode, you simply call {@link #wait} and let the progress bar run indefinitely, only clearing it
83328  * once the operation is complete.  You can optionally have the progress bar wait for a specific amount of time
83329  * and then clear itself.  Automatic mode is most appropriate for timed operations or asynchronous operations in
83330  * which you have no need for indicating intermediate progress.</p>
83331  * {@img Ext.ProgressBar/Ext.ProgressBar.png Ext.ProgressBar component}
83332  * Example Usage:
83333      var p = Ext.create('Ext.ProgressBar', {
83334        renderTo: Ext.getBody(),
83335        width: 300
83336     });
83337
83338     //Wait for 5 seconds, then update the status el (progress bar will auto-reset)
83339     p.wait({
83340        interval: 500, //bar will move fast!
83341        duration: 50000,
83342        increment: 15,
83343        text: 'Updating...',
83344        scope: this,
83345        fn: function(){
83346           p.updateText('Done!');
83347        }
83348     });
83349  * @cfg {Float} value A floating point value between 0 and 1 (e.g., .5, defaults to 0)
83350  * @cfg {String} text The progress bar text (defaults to '')
83351  * @cfg {Mixed} textEl The element to render the progress text to (defaults to the progress
83352  * bar's internal text element)
83353  * @cfg {String} id The progress bar element's id (defaults to an auto-generated id)
83354  * @xtype progressbar
83355  */
83356 Ext.define('Ext.ProgressBar', {
83357     extend: 'Ext.Component',
83358     alias: 'widget.progressbar',
83359
83360     requires: [
83361         'Ext.Template',
83362         'Ext.CompositeElement',
83363         'Ext.TaskManager',
83364         'Ext.layout.component.ProgressBar'
83365     ],
83366
83367     uses: ['Ext.fx.Anim'],
83368    /**
83369     * @cfg {String} baseCls
83370     * The base CSS class to apply to the progress bar's wrapper element (defaults to 'x-progress')
83371     */
83372     baseCls: Ext.baseCSSPrefix + 'progress',
83373
83374     config: {
83375         /**
83376         * @cfg {Boolean} animate
83377         * True to animate the progress bar during transitions (defaults to false)
83378         */
83379         animate: false,
83380
83381         /**
83382          * @cfg {String} text The text shown in the progress bar (defaults to '')
83383          */
83384         text: ''
83385     },
83386
83387     // private
83388     waitTimer: null,
83389
83390     renderTpl: [
83391         '<div class="{baseCls}-text {baseCls}-text-back">',
83392             '<div>&#160;</div>',
83393         '</div>',
83394         '<div class="{baseCls}-bar">',
83395             '<div class="{baseCls}-text">',
83396                 '<div>&#160;</div>',
83397             '</div>',
83398         '</div>'
83399     ],
83400
83401     componentLayout: 'progressbar',
83402
83403     // private
83404     initComponent: function() {
83405         this.callParent();
83406
83407         this.renderSelectors = Ext.apply(this.renderSelectors || {}, {
83408             textTopEl: '.' + this.baseCls + '-text',
83409             textBackEl: '.' + this.baseCls + '-text-back',
83410             bar: '.' + this.baseCls + '-bar'
83411         });
83412
83413         this.addEvents(
83414             /**
83415              * @event update
83416              * Fires after each update interval
83417              * @param {Ext.ProgressBar} this
83418              * @param {Number} The current progress value
83419              * @param {String} The current progress text
83420              */
83421             "update"
83422         );
83423     },
83424
83425     afterRender : function() {
83426         var me = this;
83427
83428         me.textEl = me.textEl ? Ext.get(me.textEl) : me.el.select('.' + me.baseCls + '-text');
83429
83430         this.callParent(arguments);
83431
83432         if (me.value) {
83433             me.updateProgress(me.value, me.text);
83434         }
83435         else {
83436             me.updateText(me.text);
83437         }
83438     },
83439
83440     /**
83441      * Updates the progress bar value, and optionally its text.  If the text argument is not specified,
83442      * any existing text value will be unchanged.  To blank out existing text, pass ''.  Note that even
83443      * if the progress bar value exceeds 1, it will never automatically reset -- you are responsible for
83444      * determining when the progress is complete and calling {@link #reset} to clear and/or hide the control.
83445      * @param {Float} value (optional) A floating point value between 0 and 1 (e.g., .5, defaults to 0)
83446      * @param {String} text (optional) The string to display in the progress text element (defaults to '')
83447      * @param {Boolean} animate (optional) Whether to animate the transition of the progress bar. If this value is
83448      * not specified, the default for the class is used (default to false)
83449      * @return {Ext.ProgressBar} this
83450      */
83451     updateProgress: function(value, text, animate) {
83452         var newWidth;
83453         this.value = value || 0;
83454         if (text) {
83455             this.updateText(text);
83456         }
83457         if (this.rendered && !this.isDestroyed) {
83458             newWidth = Math.floor(this.value * this.el.getWidth(true));
83459             if (Ext.isForcedBorderBox) {
83460                 newWidth += this.bar.getBorderWidth("lr");
83461             }
83462             if (animate === true || (animate !== false && this.animate)) {
83463                 this.bar.stopAnimation();
83464                 this.bar.animate(Ext.apply({
83465                     to: {
83466                         width: newWidth + 'px'
83467                     }
83468                 }, this.animate));
83469             } else {
83470                 this.bar.setWidth(newWidth);
83471             }
83472         }
83473         this.fireEvent('update', this, this.value, text);
83474         return this;
83475     },
83476
83477     /**
83478      * Updates the progress bar text.  If specified, textEl will be updated, otherwise the progress
83479      * bar itself will display the updated text.
83480      * @param {String} text (optional) The string to display in the progress text element (defaults to '')
83481      * @return {Ext.ProgressBar} this
83482      */
83483     updateText: function(text) {
83484         this.text = text;
83485         if (this.rendered) {
83486             this.textEl.update(this.text);
83487         }
83488         return this;
83489     },
83490
83491     applyText : function(text) {
83492         this.updateText(text);
83493     },
83494
83495     /**
83496          * Initiates an auto-updating progress bar.  A duration can be specified, in which case the progress
83497          * bar will automatically reset after a fixed amount of time and optionally call a callback function
83498          * if specified.  If no duration is passed in, then the progress bar will run indefinitely and must
83499          * be manually cleared by calling {@link #reset}.  The wait method accepts a config object with
83500          * the following properties:
83501          * <pre>
83502     Property   Type          Description
83503     ---------- ------------  ----------------------------------------------------------------------
83504     duration   Number        The length of time in milliseconds that the progress bar should
83505                              run before resetting itself (defaults to undefined, in which case it
83506                              will run indefinitely until reset is called)
83507     interval   Number        The length of time in milliseconds between each progress update
83508                              (defaults to 1000 ms)
83509     animate    Boolean       Whether to animate the transition of the progress bar. If this value is
83510                              not specified, the default for the class is used.
83511     increment  Number        The number of progress update segments to display within the progress
83512                              bar (defaults to 10).  If the bar reaches the end and is still
83513                              updating, it will automatically wrap back to the beginning.
83514     text       String        Optional text to display in the progress bar element (defaults to '').
83515     fn         Function      A callback function to execute after the progress bar finishes auto-
83516                              updating.  The function will be called with no arguments.  This function
83517                              will be ignored if duration is not specified since in that case the
83518                              progress bar can only be stopped programmatically, so any required function
83519                              should be called by the same code after it resets the progress bar.
83520     scope      Object        The scope that is passed to the callback function (only applies when
83521                              duration and fn are both passed).
83522     </pre>
83523              *
83524              * Example usage:
83525              * <pre><code>
83526     var p = new Ext.ProgressBar({
83527        renderTo: 'my-el'
83528     });
83529
83530     //Wait for 5 seconds, then update the status el (progress bar will auto-reset)
83531     var p = Ext.create('Ext.ProgressBar', {
83532        renderTo: Ext.getBody(),
83533        width: 300
83534     });
83535
83536     //Wait for 5 seconds, then update the status el (progress bar will auto-reset)
83537     p.wait({
83538        interval: 500, //bar will move fast!
83539        duration: 50000,
83540        increment: 15,
83541        text: 'Updating...',
83542        scope: this,
83543        fn: function(){
83544           p.updateText('Done!');
83545        }
83546     });
83547
83548     //Or update indefinitely until some async action completes, then reset manually
83549     p.wait();
83550     myAction.on('complete', function(){
83551         p.reset();
83552         p.updateText('Done!');
83553     });
83554     </code></pre>
83555          * @param {Object} config (optional) Configuration options
83556          * @return {Ext.ProgressBar} this
83557          */
83558     wait: function(o) {
83559         if (!this.waitTimer) {
83560             var scope = this;
83561             o = o || {};
83562             this.updateText(o.text);
83563             this.waitTimer = Ext.TaskManager.start({
83564                 run: function(i){
83565                     var inc = o.increment || 10;
83566                     i -= 1;
83567                     this.updateProgress(((((i+inc)%inc)+1)*(100/inc))*0.01, null, o.animate);
83568                 },
83569                 interval: o.interval || 1000,
83570                 duration: o.duration,
83571                 onStop: function(){
83572                     if (o.fn) {
83573                         o.fn.apply(o.scope || this);
83574                     }
83575                     this.reset();
83576                 },
83577                 scope: scope
83578             });
83579         }
83580         return this;
83581     },
83582
83583     /**
83584      * Returns true if the progress bar is currently in a {@link #wait} operation
83585      * @return {Boolean} True if waiting, else false
83586      */
83587     isWaiting: function(){
83588         return this.waitTimer !== null;
83589     },
83590
83591     /**
83592      * Resets the progress bar value to 0 and text to empty string.  If hide = true, the progress
83593      * bar will also be hidden (using the {@link #hideMode} property internally).
83594      * @param {Boolean} hide (optional) True to hide the progress bar (defaults to false)
83595      * @return {Ext.ProgressBar} this
83596      */
83597     reset: function(hide){
83598         this.updateProgress(0);
83599         this.clearTimer();
83600         if (hide === true) {
83601             this.hide();
83602         }
83603         return this;
83604     },
83605
83606     // private
83607     clearTimer: function(){
83608         if (this.waitTimer) {
83609             this.waitTimer.onStop = null; //prevent recursion
83610             Ext.TaskManager.stop(this.waitTimer);
83611             this.waitTimer = null;
83612         }
83613     },
83614
83615     onDestroy: function(){
83616         this.clearTimer();
83617         if (this.rendered) {
83618             if (this.textEl.isComposite) {
83619                 this.textEl.clear();
83620             }
83621             Ext.destroyMembers(this, 'textEl', 'progressBar', 'textTopEl');
83622         }
83623         this.callParent();
83624     }
83625 });
83626
83627 /**
83628  * @class Ext.ShadowPool
83629  * @extends Object
83630  * Private utility class that manages the internal Shadow cache
83631  * @private
83632  */
83633 Ext.define('Ext.ShadowPool', {
83634     singleton: true,
83635     requires: ['Ext.core.DomHelper'],
83636
83637     markup: function() {
83638         if (Ext.supports.CSS3BoxShadow) {
83639             return '<div class="' + Ext.baseCSSPrefix + 'css-shadow" role="presentation"></div>';
83640         } else if (Ext.isIE) {
83641             return '<div class="' + Ext.baseCSSPrefix + 'ie-shadow" role="presentation"></div>';
83642         } else {
83643             return '<div class="' + Ext.baseCSSPrefix + 'frame-shadow" role="presentation">' +
83644                 '<div class="xst" role="presentation">' +
83645                     '<div class="xstl" role="presentation"></div>' +
83646                     '<div class="xstc" role="presentation"></div>' +
83647                     '<div class="xstr" role="presentation"></div>' +
83648                 '</div>' +
83649                 '<div class="xsc" role="presentation">' +
83650                     '<div class="xsml" role="presentation"></div>' +
83651                     '<div class="xsmc" role="presentation"></div>' +
83652                     '<div class="xsmr" role="presentation"></div>' +
83653                 '</div>' +
83654                 '<div class="xsb" role="presentation">' +
83655                     '<div class="xsbl" role="presentation"></div>' +
83656                     '<div class="xsbc" role="presentation"></div>' +
83657                     '<div class="xsbr" role="presentation"></div>' +
83658                 '</div>' +
83659             '</div>';
83660         }
83661     }(),
83662
83663     shadows: [],
83664
83665     pull: function() {
83666         var sh = this.shadows.shift();
83667         if (!sh) {
83668             sh = Ext.get(Ext.core.DomHelper.insertHtml("beforeBegin", document.body.firstChild, this.markup));
83669             sh.autoBoxAdjust = false;
83670         }
83671         return sh;
83672     },
83673
83674     push: function(sh) {
83675         this.shadows.push(sh);
83676     },
83677     
83678     reset: function() {
83679         Ext.Array.each(this.shadows, function(shadow) {
83680             shadow.remove();
83681         });
83682         this.shadows = [];
83683     }
83684 });
83685 /**
83686  * @class Ext.Shadow
83687  * Simple class that can provide a shadow effect for any element.  Note that the element MUST be absolutely positioned,
83688  * and the shadow does not provide any shimming.  This should be used only in simple cases -- for more advanced
83689  * functionality that can also provide the same shadow effect, see the {@link Ext.Layer} class.
83690  * @constructor
83691  * Create a new Shadow
83692  * @param {Object} config The config object
83693  */
83694 Ext.define('Ext.Shadow', {
83695     requires: ['Ext.ShadowPool'],
83696
83697     constructor: function(config) {
83698         Ext.apply(this, config);
83699         if (typeof this.mode != "string") {
83700             this.mode = this.defaultMode;
83701         }
83702         var offset = this.offset,
83703             adjusts = {
83704                 h: 0
83705             },
83706             rad = Math.floor(this.offset / 2);
83707
83708         switch (this.mode.toLowerCase()) {
83709             // all this hideous nonsense calculates the various offsets for shadows
83710             case "drop":
83711                 if (Ext.supports.CSS3BoxShadow) {
83712                     adjusts.w = adjusts.h = -offset;
83713                     adjusts.l = adjusts.t = offset;
83714                 } else {
83715                     adjusts.w = 0;
83716                     adjusts.l = adjusts.t = offset;
83717                     adjusts.t -= 1;
83718                     if (Ext.isIE) {
83719                         adjusts.l -= offset + rad;
83720                         adjusts.t -= offset + rad;
83721                         adjusts.w -= rad;
83722                         adjusts.h -= rad;
83723                         adjusts.t += 1;
83724                     }
83725                 }
83726                 break;
83727             case "sides":
83728                 if (Ext.supports.CSS3BoxShadow) {
83729                     adjusts.h -= offset;
83730                     adjusts.t = offset;
83731                     adjusts.l = adjusts.w = 0;
83732                 } else {
83733                     adjusts.w = (offset * 2);
83734                     adjusts.l = -offset;
83735                     adjusts.t = offset - 1;
83736                     if (Ext.isIE) {
83737                         adjusts.l -= (offset - rad);
83738                         adjusts.t -= offset + rad;
83739                         adjusts.l += 1;
83740                         adjusts.w -= (offset - rad) * 2;
83741                         adjusts.w -= rad + 1;
83742                         adjusts.h -= 1;
83743                     }
83744                 }
83745                 break;
83746             case "frame":
83747                 if (Ext.supports.CSS3BoxShadow) {
83748                     adjusts.l = adjusts.w = adjusts.t = 0;
83749                 } else {
83750                     adjusts.w = adjusts.h = (offset * 2);
83751                     adjusts.l = adjusts.t = -offset;
83752                     adjusts.t += 1;
83753                     adjusts.h -= 2;
83754                     if (Ext.isIE) {
83755                         adjusts.l -= (offset - rad);
83756                         adjusts.t -= (offset - rad);
83757                         adjusts.l += 1;
83758                         adjusts.w -= (offset + rad + 1);
83759                         adjusts.h -= (offset + rad);
83760                         adjusts.h += 1;
83761                     }
83762                     break;
83763                 }
83764         }
83765         this.adjusts = adjusts;
83766     },
83767
83768     /**
83769      * @cfg {String} mode
83770      * The shadow display mode.  Supports the following options:<div class="mdetail-params"><ul>
83771      * <li><b><tt>sides</tt></b> : Shadow displays on both sides and bottom only</li>
83772      * <li><b><tt>frame</tt></b> : Shadow displays equally on all four sides</li>
83773      * <li><b><tt>drop</tt></b> : Traditional bottom-right drop shadow</li>
83774      * </ul></div>
83775      */
83776     /**
83777      * @cfg {String} offset
83778      * The number of pixels to offset the shadow from the element (defaults to <tt>4</tt>)
83779      */
83780     offset: 4,
83781
83782     // private
83783     defaultMode: "drop",
83784
83785     /**
83786      * Displays the shadow under the target element
83787      * @param {Mixed} targetEl The id or element under which the shadow should display
83788      */
83789     show: function(target) {
83790         target = Ext.get(target);
83791         if (!this.el) {
83792             this.el = Ext.ShadowPool.pull();
83793             if (this.el.dom.nextSibling != target.dom) {
83794                 this.el.insertBefore(target);
83795             }
83796         }
83797         this.el.setStyle("z-index", this.zIndex || parseInt(target.getStyle("z-index"), 10) - 1);
83798         if (Ext.isIE && !Ext.supports.CSS3BoxShadow) {
83799             this.el.dom.style.filter = "progid:DXImageTransform.Microsoft.alpha(opacity=50) progid:DXImageTransform.Microsoft.Blur(pixelradius=" + (this.offset) + ")";
83800         }
83801         this.realign(
83802             target.getLeft(true),
83803             target.getTop(true),
83804             target.getWidth(),
83805             target.getHeight()
83806         );
83807         this.el.dom.style.display = "block";
83808     },
83809
83810     /**
83811      * Returns true if the shadow is visible, else false
83812      */
83813     isVisible: function() {
83814         return this.el ? true: false;
83815     },
83816
83817     /**
83818      * Direct alignment when values are already available. Show must be called at least once before
83819      * calling this method to ensure it is initialized.
83820      * @param {Number} left The target element left position
83821      * @param {Number} top The target element top position
83822      * @param {Number} width The target element width
83823      * @param {Number} height The target element height
83824      */
83825     realign: function(l, t, targetWidth, targetHeight) {
83826         if (!this.el) {
83827             return;
83828         }
83829         var adjusts = this.adjusts,
83830             d = this.el.dom,
83831             targetStyle = d.style,
83832             shadowWidth,
83833             shadowHeight,
83834             cn,
83835             sww, 
83836             sws, 
83837             shs;
83838
83839         targetStyle.left = (l + adjusts.l) + "px";
83840         targetStyle.top = (t + adjusts.t) + "px";
83841         shadowWidth = Math.max(targetWidth + adjusts.w, 0);
83842         shadowHeight = Math.max(targetHeight + adjusts.h, 0);
83843         sws = shadowWidth + "px";
83844         shs = shadowHeight + "px";
83845         if (targetStyle.width != sws || targetStyle.height != shs) {
83846             targetStyle.width = sws;
83847             targetStyle.height = shs;
83848             if (Ext.supports.CSS3BoxShadow) {
83849                 targetStyle.boxShadow = '0 0 ' + this.offset + 'px 0 #888';
83850             } else {
83851
83852                 // Adjust the 9 point framed element to poke out on the required sides
83853                 if (!Ext.isIE) {
83854                     cn = d.childNodes;
83855                     sww = Math.max(0, (shadowWidth - 12)) + "px";
83856                     cn[0].childNodes[1].style.width = sww;
83857                     cn[1].childNodes[1].style.width = sww;
83858                     cn[2].childNodes[1].style.width = sww;
83859                     cn[1].style.height = Math.max(0, (shadowHeight - 12)) + "px";
83860                 }
83861             }
83862         }
83863     },
83864
83865     /**
83866      * Hides this shadow
83867      */
83868     hide: function() {
83869         if (this.el) {
83870             this.el.dom.style.display = "none";
83871             Ext.ShadowPool.push(this.el);
83872             delete this.el;
83873         }
83874     },
83875
83876     /**
83877      * Adjust the z-index of this shadow
83878      * @param {Number} zindex The new z-index
83879      */
83880     setZIndex: function(z) {
83881         this.zIndex = z;
83882         if (this.el) {
83883             this.el.setStyle("z-index", z);
83884         }
83885     }
83886 });
83887 /**
83888  * @class Ext.button.Split
83889  * @extends Ext.button.Button
83890  * A split button that provides a built-in dropdown arrow that can fire an event separately from the default
83891  * click event of the button.  Typically this would be used to display a dropdown menu that provides additional
83892  * options to the primary button action, but any custom handler can provide the arrowclick implementation.  
83893  * {@img Ext.button.Split/Ext.button.Split.png Ext.button.Split component}
83894  * Example usage:
83895  * <pre><code>
83896 // display a dropdown menu:
83897     Ext.create('Ext.button.Split', {
83898         renderTo: 'button-ct', // the container id
83899         text: 'Options',
83900         handler: optionsHandler, // handle a click on the button itself
83901         menu: new Ext.menu.Menu({
83902         items: [
83903                 // these items will render as dropdown menu items when the arrow is clicked:
83904                 {text: 'Item 1', handler: item1Handler},
83905                 {text: 'Item 2', handler: item2Handler}
83906         ]
83907         })
83908     });
83909
83910 // Instead of showing a menu, you provide any type of custom
83911 // functionality you want when the dropdown arrow is clicked:
83912     Ext.create('Ext.button.Split', {
83913         renderTo: 'button-ct',
83914         text: 'Options',
83915         handler: optionsHandler,
83916         arrowHandler: myCustomHandler
83917     });
83918 </code></pre>
83919  * @cfg {Function} arrowHandler A function called when the arrow button is clicked (can be used instead of click event)
83920  * @cfg {String} arrowTooltip The title attribute of the arrow
83921  * @constructor
83922  * Create a new menu button
83923  * @param {Object} config The config object
83924  * @xtype splitbutton
83925  */
83926
83927 Ext.define('Ext.button.Split', {
83928
83929     /* Begin Definitions */
83930
83931     alias: 'widget.splitbutton',
83932
83933     extend: 'Ext.button.Button',
83934     alternateClassName: 'Ext.SplitButton',
83935
83936     // private
83937     arrowCls      : 'split',
83938     split         : true,
83939
83940     // private
83941     initComponent : function(){
83942         this.callParent();
83943         /**
83944          * @event arrowclick
83945          * Fires when this button's arrow is clicked
83946          * @param {MenuButton} this
83947          * @param {EventObject} e The click event
83948          */
83949         this.addEvents("arrowclick");
83950     },
83951
83952      /**
83953      * Sets this button's arrow click handler.
83954      * @param {Function} handler The function to call when the arrow is clicked
83955      * @param {Object} scope (optional) Scope for the function passed above
83956      */
83957     setArrowHandler : function(handler, scope){
83958         this.arrowHandler = handler;
83959         this.scope = scope;
83960     },
83961
83962     // private
83963     onClick : function(e, t) {
83964         var me = this;
83965         
83966         e.preventDefault();
83967         if (!me.disabled) {
83968             if (me.overMenuTrigger) {
83969                 if (me.menu && !me.menu.isVisible() && !me.ignoreNextClick) {
83970                     me.showMenu();
83971                 }
83972                 me.fireEvent("arrowclick", me, e);
83973                 if (me.arrowHandler) {
83974                     me.arrowHandler.call(me.scope || me, me, e);
83975                 }
83976             } else {
83977                 if (me.enableToggle) {
83978                     me.toggle();
83979                 }
83980                 me.fireEvent("click", me, e);
83981                 if (me.handler) {
83982                     me.handler.call(me.scope || me, me, e);
83983                 }
83984                 me.onBlur();
83985             }
83986         }
83987     }
83988 });
83989 /**
83990  * @class Ext.button.Cycle
83991  * @extends Ext.button.Split
83992  * A specialized SplitButton that contains a menu of {@link Ext.menu.CheckItem} elements.  The button automatically
83993  * cycles through each menu item on click, raising the button's {@link #change} event (or calling the button's
83994  * {@link #changeHandler} function, if supplied) for the active menu item. Clicking on the arrow section of the
83995  * button displays the dropdown menu just like a normal SplitButton.  
83996  * {@img Ext.button.Cycle/Ext.button.Cycle.png Ext.button.Cycle component}
83997  * Example usage:
83998  * <pre><code>
83999     Ext.create('Ext.button.Cycle', {
84000         showText: true,
84001         prependText: 'View as ',
84002         renderTo: Ext.getBody(),
84003         menu: {
84004             id: 'view-type-menu',
84005             items: [{
84006                 text:'text only',
84007                 iconCls:'view-text',
84008                 checked:true
84009             },{
84010                 text:'HTML',
84011                 iconCls:'view-html'
84012             }]
84013         },
84014         changeHandler:function(cycleBtn, activeItem){
84015             Ext.Msg.alert('Change View', activeItem.text);
84016         }
84017     });
84018 </code></pre>
84019  * @constructor
84020  * Create a new split button
84021  * @param {Object} config The config object
84022  * @xtype cycle
84023  */
84024
84025 Ext.define('Ext.button.Cycle', {
84026
84027     /* Begin Definitions */
84028
84029     alias: 'widget.cycle',
84030
84031     extend: 'Ext.button.Split',
84032     alternateClassName: 'Ext.CycleButton',
84033
84034     /* End Definitions */
84035
84036     /**
84037      * @cfg {Array} items <p>Deprecated as of 4.0. Use the {@link #menu} config instead. All menu items will be created
84038      * as {@link Ext.menu.CheckItem CheckItem}s.</p>
84039      * <p>An array of {@link Ext.menu.CheckItem} <b>config</b> objects to be used when creating the
84040      * button's menu items (e.g., {text:'Foo', iconCls:'foo-icon'})
84041      */
84042     /**
84043      * @cfg {Boolean} showText True to display the active item's text as the button text (defaults to false).
84044      * The Button will show its configured {@link #text} if this. config is omitted.
84045      */
84046     /**
84047      * @cfg {String} prependText A static string to prepend before the active item's text when displayed as the
84048      * button's text (only applies when showText = true, defaults to '')
84049      */
84050     /**
84051      * @cfg {Function} changeHandler A callback function that will be invoked each time the active menu
84052      * item in the button's menu has changed.  If this callback is not supplied, the SplitButton will instead
84053      * fire the {@link #change} event on active item change.  The changeHandler function will be called with the
84054      * following argument list: (SplitButton this, Ext.menu.CheckItem item)
84055      */
84056     /**
84057      * @cfg {String} forceIcon A css class which sets an image to be used as the static icon for this button.  This
84058      * icon will always be displayed regardless of which item is selected in the dropdown list.  This overrides the 
84059      * default behavior of changing the button's icon to match the selected item's icon on change.
84060      */
84061     /**
84062      * @property menu
84063      * @type Menu
84064      * The {@link Ext.menu.Menu Menu} object used to display the {@link Ext.menu.CheckItem CheckItems} representing the available choices.
84065      */
84066
84067     // private
84068     getButtonText: function(item) {
84069         var me = this,
84070             text = '';
84071
84072         if (item && me.showText === true) {
84073             if (me.prependText) {
84074                 text += me.prependText;
84075             }
84076             text += item.text;
84077             return text;
84078         }
84079         return me.text;
84080     },
84081
84082     /**
84083      * Sets the button's active menu item.
84084      * @param {Ext.menu.CheckItem} item The item to activate
84085      * @param {Boolean} suppressEvent True to prevent the button's change event from firing (defaults to false)
84086      */
84087     setActiveItem: function(item, suppressEvent) {
84088         var me = this;
84089
84090         if (!Ext.isObject(item)) {
84091             item = me.menu.getComponent(item);
84092         }
84093         if (item) {
84094             if (!me.rendered) {
84095                 me.text = me.getButtonText(item);
84096                 me.iconCls = item.iconCls;
84097             } else {
84098                 me.setText(me.getButtonText(item));
84099                 me.setIconCls(item.iconCls);
84100             }
84101             me.activeItem = item;
84102             if (!item.checked) {
84103                 item.setChecked(true, false);
84104             }
84105             if (me.forceIcon) {
84106                 me.setIconCls(me.forceIcon);
84107             }
84108             if (!suppressEvent) {
84109                 me.fireEvent('change', me, item);
84110             }
84111         }
84112     },
84113
84114     /**
84115      * Gets the currently active menu item.
84116      * @return {Ext.menu.CheckItem} The active item
84117      */
84118     getActiveItem: function() {
84119         return this.activeItem;
84120     },
84121
84122     // private
84123     initComponent: function() {
84124         var me = this,
84125             checked = 0,
84126             items;
84127
84128         me.addEvents(
84129             /**
84130              * @event change
84131              * Fires after the button's active menu item has changed.  Note that if a {@link #changeHandler} function
84132              * is set on this CycleButton, it will be called instead on active item change and this change event will
84133              * not be fired.
84134              * @param {Ext.button.Cycle} this
84135              * @param {Ext.menu.CheckItem} item The menu item that was selected
84136              */
84137             "change"
84138         );
84139
84140         if (me.changeHandler) {
84141             me.on('change', me.changeHandler, me.scope || me);
84142             delete me.changeHandler;
84143         }
84144
84145         // Allow them to specify a menu config which is a standard Button config.
84146         // Remove direct use of "items" in 5.0.
84147         items = (me.menu.items||[]).concat(me.items||[]);
84148         me.menu = Ext.applyIf({
84149             cls: Ext.baseCSSPrefix + 'cycle-menu',
84150             items: []
84151         }, me.menu);
84152
84153         // Convert all items to CheckItems
84154         Ext.each(items, function(item, i) {
84155             item = Ext.applyIf({
84156                 group: me.id,
84157                 itemIndex: i,
84158                 checkHandler: me.checkHandler,
84159                 scope: me,
84160                 checked: item.checked || false
84161             }, item);
84162             me.menu.items.push(item);
84163             if (item.checked) {
84164                 checked = i;
84165             }
84166         });
84167         me.itemCount = me.menu.items.length;
84168         me.callParent(arguments);
84169         me.on('click', me.toggleSelected, me);
84170         me.setActiveItem(checked, me);
84171
84172         // If configured with a fixed width, the cycling will center a different child item's text each click. Prevent this.
84173         if (me.width && me.showText) {
84174             me.addCls(Ext.baseCSSPrefix + 'cycle-fixed-width');
84175         }
84176     },
84177
84178     // private
84179     checkHandler: function(item, pressed) {
84180         if (pressed) {
84181             this.setActiveItem(item);
84182         }
84183     },
84184
84185     /**
84186      * This is normally called internally on button click, but can be called externally to advance the button's
84187      * active item programmatically to the next one in the menu.  If the current item is the last one in the menu
84188      * the active item will be set to the first item in the menu.
84189      */
84190     toggleSelected: function() {
84191         var me = this,
84192             m = me.menu,
84193             checkItem;
84194
84195         checkItem = me.activeItem.next(':not([disabled])') || m.items.getAt(0);
84196         checkItem.setChecked(true);
84197     }
84198 });
84199 /**
84200  * @class Ext.container.ButtonGroup
84201  * @extends Ext.panel.Panel
84202  * <p>Provides a container for arranging a group of related Buttons in a tabular manner.</p>
84203  * Example usage:
84204  * {@img Ext.container.ButtonGroup/Ext.container.ButtonGroup.png Ext.container.ButtonGroup component}
84205  * <pre><code>
84206     Ext.create('Ext.panel.Panel', {
84207         title: 'Panel with ButtonGroup',
84208         width: 300,
84209         height:200,
84210         renderTo: document.body,
84211         html: 'HTML Panel Content',
84212         tbar: [{
84213             xtype: 'buttongroup',
84214             columns: 3,
84215             title: 'Clipboard',
84216             items: [{
84217                 text: 'Paste',
84218                 scale: 'large',
84219                 rowspan: 3,
84220                 iconCls: 'add',
84221                 iconAlign: 'top',
84222                 cls: 'x-btn-as-arrow'
84223             },{
84224                 xtype:'splitbutton',
84225                 text: 'Menu Button',
84226                 scale: 'large',
84227                 rowspan: 3,
84228                 iconCls: 'add',
84229                 iconAlign: 'top',
84230                 arrowAlign:'bottom',
84231                 menu: [{text: 'Menu Item 1'}]
84232             },{
84233                 xtype:'splitbutton', text: 'Cut', iconCls: 'add16', menu: [{text: 'Cut Menu Item'}]
84234             },{
84235                 text: 'Copy', iconCls: 'add16'
84236             },{
84237                 text: 'Format', iconCls: 'add16'
84238             }]
84239         }]
84240     });
84241  * </code></pre>
84242  * @constructor
84243  * Create a new ButtonGroup.
84244  * @param {Object} config The config object
84245  * @xtype buttongroup
84246  */
84247 Ext.define('Ext.container.ButtonGroup', {
84248     extend: 'Ext.panel.Panel',
84249     alias: 'widget.buttongroup',
84250     alternateClassName: 'Ext.ButtonGroup',
84251
84252     /**
84253      * @cfg {Number} columns The <tt>columns</tt> configuration property passed to the
84254      * {@link #layout configured layout manager}. See {@link Ext.layout.container.Table#columns}.
84255      */
84256
84257     /**
84258      * @cfg {String} baseCls  Defaults to <tt>'x-btn-group'</tt>.  See {@link Ext.panel.Panel#baseCls}.
84259      */
84260     baseCls: Ext.baseCSSPrefix + 'btn-group',
84261
84262     /**
84263      * @cfg {Object} layout  Defaults to <tt>'table'</tt>.  See {@link Ext.container.Container#layout}.
84264      */
84265     layout: {
84266         type: 'table'
84267     },
84268
84269     defaultType: 'button',
84270
84271     /**
84272      * @cfg {Boolean} frame  Defaults to <tt>true</tt>.  See {@link Ext.panel.Panel#frame}.
84273      */
84274     frame: true,
84275     
84276     frameHeader: false,
84277     
84278     internalDefaults: {removeMode: 'container', hideParent: true},
84279
84280     initComponent : function(){
84281         // Copy the component's columns config to the layout if specified
84282         var me = this,
84283             cols = me.columns;
84284
84285         me.noTitleCls = me.baseCls + '-notitle';
84286         if (cols) {
84287             me.layout = Ext.apply({}, {columns: cols}, me.layout);
84288         }
84289
84290         if (!me.title) {
84291             me.addCls(me.noTitleCls);
84292         }
84293         me.callParent(arguments);
84294     },
84295
84296     afterLayout: function() {
84297         var me = this;
84298         
84299         me.callParent(arguments);
84300
84301         // Pugly hack for a pugly browser:
84302         // If not an explicitly set width, then size the width to match the inner table
84303         if (me.layout.table && (Ext.isIEQuirks || Ext.isIE6) && !me.width) {
84304             var t = me.getTargetEl();
84305             t.setWidth(me.layout.table.offsetWidth + t.getPadding('lr'));
84306         }
84307     },
84308
84309     afterRender: function() {
84310         var me = this;
84311         
84312         //we need to add an addition item in here so the ButtonGroup title is centered
84313         if (me.header) {
84314             me.header.insert(0, {
84315                 xtype: 'component',
84316                 ui   : me.ui,
84317                 html : '&nbsp;',
84318                 flex : 1
84319             });
84320         }
84321         
84322         me.callParent(arguments);
84323     },
84324     
84325     // private
84326     onBeforeAdd: function(component) {
84327         if (component.is('button')) {
84328             component.ui = component.ui + '-toolbar';
84329         }
84330         this.callParent(arguments);
84331     },
84332
84333     //private
84334     applyDefaults: function(c) {
84335         if (!Ext.isString(c)) {
84336             c = this.callParent(arguments);
84337             var d = this.internalDefaults;
84338             if (c.events) {
84339                 Ext.applyIf(c.initialConfig, d);
84340                 Ext.apply(c, d);
84341             } else {
84342                 Ext.applyIf(c, d);
84343             }
84344         }
84345         return c;
84346     }
84347
84348     /**
84349      * @cfg {Array} tools  @hide
84350      */
84351     /**
84352      * @cfg {Boolean} collapsible  @hide
84353      */
84354     /**
84355      * @cfg {Boolean} collapseMode  @hide
84356      */
84357     /**
84358      * @cfg {Boolean} animCollapse  @hide
84359      */
84360     /**
84361      * @cfg {Boolean} closable  @hide
84362      */
84363 });
84364
84365 /**
84366  * @class Ext.container.Viewport
84367  * @extends Ext.container.Container
84368
84369 A specialized container representing the viewable application area (the browser viewport).
84370
84371 The Viewport renders itself to the document body, and automatically sizes itself to the size of
84372 the browser viewport and manages window resizing. There may only be one Viewport created
84373 in a page.
84374
84375 Like any {@link Ext.container.Container Container}, a Viewport will only perform sizing and positioning
84376 on its child Components if you configure it with a {@link #layout}.
84377
84378 A Common layout used with Viewports is {@link Ext.layout.container.Border border layout}, but if the
84379 required layout is simpler, a different layout should be chosen.
84380
84381 For example, to simply make a single child item occupy all available space, use {@link Ext.layout.container.Fit fit layout}.
84382
84383 To display one "active" item at full size from a choice of several child items, use {@link Ext.layout.container.Card card layout}.
84384
84385 Inner layouts are available by virtue of the fact that all {@link Ext.panel.Panel Panel}s
84386 added to the Viewport, either through its {@link #items}, or through the items, or the {@link #add}
84387 method of any of its child Panels may themselves have a layout.
84388
84389 The Viewport does not provide scrolling, so child Panels within the Viewport should provide
84390 for scrolling if needed using the {@link #autoScroll} config.
84391 {@img Ext.container.Viewport/Ext.container.Viewport.png Ext.container.Viewport component}
84392 An example showing a classic application border layout:
84393
84394     Ext.create('Ext.container.Viewport', {
84395         layout: 'border',
84396         renderTo: Ext.getBody(),
84397         items: [{
84398             region: 'north',
84399             html: '<h1 class="x-panel-header">Page Title</h1>',
84400             autoHeight: true,
84401             border: false,
84402             margins: '0 0 5 0'
84403         }, {
84404             region: 'west',
84405             collapsible: true,
84406             title: 'Navigation',
84407             width: 150
84408             // could use a TreePanel or AccordionLayout for navigational items
84409         }, {
84410             region: 'south',
84411             title: 'South Panel',
84412             collapsible: true,
84413             html: 'Information goes here',
84414             split: true,
84415             height: 100,
84416             minHeight: 100
84417         }, {
84418             region: 'east',
84419             title: 'East Panel',
84420             collapsible: true,
84421             split: true,
84422             width: 150
84423         }, {
84424             region: 'center',
84425             xtype: 'tabpanel', // TabPanel itself has no title
84426             activeTab: 0,      // First tab active by default
84427             items: {
84428                 title: 'Default Tab',
84429                 html: 'The first tab\'s content. Others may be added dynamically'
84430             }
84431         }]
84432     });
84433
84434  * @constructor
84435  * Create a new Viewport
84436  * @param {Object} config The config object
84437  * @markdown
84438  * @xtype viewport
84439  */
84440 Ext.define('Ext.container.Viewport', {
84441     extend: 'Ext.container.Container',
84442     alias: 'widget.viewport',
84443     requires: ['Ext.EventManager'],
84444     alternateClassName: 'Ext.Viewport',
84445
84446     /*
84447      * Privatize config options which, if used, would interfere with the
84448      * correct operation of the Viewport as the sole manager of the
84449      * layout of the document body.
84450      */
84451     /**
84452      * @cfg {Mixed} applyTo @hide
84453      */
84454     /**
84455      * @cfg {Boolean} allowDomMove @hide
84456      */
84457     /**
84458      * @cfg {Boolean} hideParent @hide
84459      */
84460     /**
84461      * @cfg {Mixed} renderTo @hide
84462      */
84463     /**
84464      * @cfg {Boolean} hideParent @hide
84465      */
84466     /**
84467      * @cfg {Number} height @hide
84468      */
84469     /**
84470      * @cfg {Number} width @hide
84471      */
84472     /**
84473      * @cfg {Boolean} autoHeight @hide
84474      */
84475     /**
84476      * @cfg {Boolean} autoWidth @hide
84477      */
84478     /**
84479      * @cfg {Boolean} deferHeight @hide
84480      */
84481     /**
84482      * @cfg {Boolean} monitorResize @hide
84483      */
84484
84485     isViewport: true,
84486
84487     ariaRole: 'application',
84488     initComponent : function() {
84489         var me = this,
84490             html = Ext.fly(document.body.parentNode),
84491             el;
84492         me.callParent(arguments);
84493         html.addCls(Ext.baseCSSPrefix + 'viewport');
84494         if (me.autoScroll) {
84495             html.setStyle('overflow', 'auto');
84496         }
84497         me.el = el = Ext.getBody();
84498         el.setHeight = Ext.emptyFn;
84499         el.setWidth = Ext.emptyFn;
84500         el.setSize = Ext.emptyFn;
84501         el.dom.scroll = 'no';
84502         me.allowDomMove = false;
84503         //this.autoWidth = true;
84504         //this.autoHeight = true;
84505         Ext.EventManager.onWindowResize(me.fireResize, me);
84506         me.renderTo = me.el;
84507     },
84508
84509     fireResize : function(w, h){
84510         // setSize is the single entry point to layouts
84511         this.setSize(w, h);
84512         //this.fireEvent('resize', this, w, h, w, h);
84513     }
84514 });
84515
84516 /*
84517  * This is a derivative of the similarly named class in the YUI Library.
84518  * The original license:
84519  * Copyright (c) 2006, Yahoo! Inc. All rights reserved.
84520  * Code licensed under the BSD License:
84521  * http://developer.yahoo.net/yui/license.txt
84522  */
84523
84524
84525 /**
84526  * @class Ext.dd.DDTarget
84527  * A DragDrop implementation that does not move, but can be a drop
84528  * target.  You would get the same result by simply omitting implementation
84529  * for the event callbacks, but this way we reduce the processing cost of the
84530  * event listener and the callbacks.
84531  * @extends Ext.dd.DragDrop
84532  * @constructor
84533  * @param {String} id the id of the element that is a drop target
84534  * @param {String} sGroup the group of related DragDrop objects
84535  * @param {object} config an object containing configurable attributes
84536  *                 Valid properties for DDTarget in addition to those in
84537  *                 DragDrop:
84538  *                    none
84539  */
84540 Ext.define('Ext.dd.DDTarget', {
84541     extend: 'Ext.dd.DragDrop',
84542     constructor: function(id, sGroup, config) {
84543         if (id) {
84544             this.initTarget(id, sGroup, config);
84545         }
84546     },
84547
84548     /**
84549      * @hide
84550      * Overridden and disabled. A DDTarget does not support being dragged.
84551      * @method
84552      */
84553     getDragEl: Ext.emptyFn,
84554     /**
84555      * @hide
84556      * Overridden and disabled. A DDTarget does not support being dragged.
84557      * @method
84558      */
84559     isValidHandleChild: Ext.emptyFn,
84560     /**
84561      * @hide
84562      * Overridden and disabled. A DDTarget does not support being dragged.
84563      * @method
84564      */
84565     startDrag: Ext.emptyFn,
84566     /**
84567      * @hide
84568      * Overridden and disabled. A DDTarget does not support being dragged.
84569      * @method
84570      */
84571     endDrag: Ext.emptyFn,
84572     /**
84573      * @hide
84574      * Overridden and disabled. A DDTarget does not support being dragged.
84575      * @method
84576      */
84577     onDrag: Ext.emptyFn,
84578     /**
84579      * @hide
84580      * Overridden and disabled. A DDTarget does not support being dragged.
84581      * @method
84582      */
84583     onDragDrop: Ext.emptyFn,
84584     /**
84585      * @hide
84586      * Overridden and disabled. A DDTarget does not support being dragged.
84587      * @method
84588      */
84589     onDragEnter: Ext.emptyFn,
84590     /**
84591      * @hide
84592      * Overridden and disabled. A DDTarget does not support being dragged.
84593      * @method
84594      */
84595     onDragOut: Ext.emptyFn,
84596     /**
84597      * @hide
84598      * Overridden and disabled. A DDTarget does not support being dragged.
84599      * @method
84600      */
84601     onDragOver: Ext.emptyFn,
84602     /**
84603      * @hide
84604      * Overridden and disabled. A DDTarget does not support being dragged.
84605      * @method
84606      */
84607     onInvalidDrop: Ext.emptyFn,
84608     /**
84609      * @hide
84610      * Overridden and disabled. A DDTarget does not support being dragged.
84611      * @method
84612      */
84613     onMouseDown: Ext.emptyFn,
84614     /**
84615      * @hide
84616      * Overridden and disabled. A DDTarget does not support being dragged.
84617      * @method
84618      */
84619     onMouseUp: Ext.emptyFn,
84620     /**
84621      * @hide
84622      * Overridden and disabled. A DDTarget does not support being dragged.
84623      * @method
84624      */
84625     setXConstraint: Ext.emptyFn,
84626     /**
84627      * @hide
84628      * Overridden and disabled. A DDTarget does not support being dragged.
84629      * @method
84630      */
84631     setYConstraint: Ext.emptyFn,
84632     /**
84633      * @hide
84634      * Overridden and disabled. A DDTarget does not support being dragged.
84635      * @method
84636      */
84637     resetConstraints: Ext.emptyFn,
84638     /**
84639      * @hide
84640      * Overridden and disabled. A DDTarget does not support being dragged.
84641      * @method
84642      */
84643     clearConstraints: Ext.emptyFn,
84644     /**
84645      * @hide
84646      * Overridden and disabled. A DDTarget does not support being dragged.
84647      * @method
84648      */
84649     clearTicks: Ext.emptyFn,
84650     /**
84651      * @hide
84652      * Overridden and disabled. A DDTarget does not support being dragged.
84653      * @method
84654      */
84655     setInitPosition: Ext.emptyFn,
84656     /**
84657      * @hide
84658      * Overridden and disabled. A DDTarget does not support being dragged.
84659      * @method
84660      */
84661     setDragElId: Ext.emptyFn,
84662     /**
84663      * @hide
84664      * Overridden and disabled. A DDTarget does not support being dragged.
84665      * @method
84666      */
84667     setHandleElId: Ext.emptyFn,
84668     /**
84669      * @hide
84670      * Overridden and disabled. A DDTarget does not support being dragged.
84671      * @method
84672      */
84673     setOuterHandleElId: Ext.emptyFn,
84674     /**
84675      * @hide
84676      * Overridden and disabled. A DDTarget does not support being dragged.
84677      * @method
84678      */
84679     addInvalidHandleClass: Ext.emptyFn,
84680     /**
84681      * @hide
84682      * Overridden and disabled. A DDTarget does not support being dragged.
84683      * @method
84684      */
84685     addInvalidHandleId: Ext.emptyFn,
84686     /**
84687      * @hide
84688      * Overridden and disabled. A DDTarget does not support being dragged.
84689      * @method
84690      */
84691     addInvalidHandleType: Ext.emptyFn,
84692     /**
84693      * @hide
84694      * Overridden and disabled. A DDTarget does not support being dragged.
84695      * @method
84696      */
84697     removeInvalidHandleClass: Ext.emptyFn,
84698     /**
84699      * @hide
84700      * Overridden and disabled. A DDTarget does not support being dragged.
84701      * @method
84702      */
84703     removeInvalidHandleId: Ext.emptyFn,
84704     /**
84705      * @hide
84706      * Overridden and disabled. A DDTarget does not support being dragged.
84707      * @method
84708      */
84709     removeInvalidHandleType: Ext.emptyFn,
84710
84711     toString: function() {
84712         return ("DDTarget " + this.id);
84713     }
84714 });
84715 /**
84716  * @class Ext.dd.DragTracker
84717  * A DragTracker listens for drag events on an Element and fires events at the start and end of the drag,
84718  * as well as during the drag. This is useful for components such as {@link Ext.slider.Multi}, where there is
84719  * an element that can be dragged around to change the Slider's value.
84720  * DragTracker provides a series of template methods that should be overridden to provide functionality
84721  * in response to detected drag operations. These are onBeforeStart, onStart, onDrag and onEnd.
84722  * See {@link Ext.slider.Multi}'s initEvents function for an example implementation.
84723  */
84724 Ext.define('Ext.dd.DragTracker', {
84725
84726     uses: ['Ext.util.Region'],
84727
84728     mixins: {
84729         observable: 'Ext.util.Observable'
84730     },
84731
84732     /**
84733      * @property active
84734      * @type Boolean
84735      * Read-only property indicated whether the user is currently dragging this
84736      * tracker.
84737      */
84738     active: false,
84739
84740     /**
84741      * @property dragTarget
84742      * @type HtmlElement
84743      * <p><b>Only valid during drag operations. Read-only.</b></p>
84744      * <p>The element being dragged.</p>
84745      * <p>If the {@link #delegate} option is used, this will be the delegate element which was mousedowned.</p>
84746      */
84747
84748     /**
84749      * @cfg {Boolean} trackOver
84750      * <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>
84751      * <p>This is implicitly set when an {@link #overCls} is specified.</p>
84752      * <b>If the {@link #delegate} option is used, these events fire only when a delegate element is entered of left.</b>.
84753      */
84754     trackOver: false,
84755
84756     /**
84757      * @cfg {String} overCls
84758      * <p>A CSS class to add to the DragTracker's target element when the element (or, if the {@link #delegate} option is used,
84759      * when a delegate element) is mouseovered.</p>
84760      * <b>If the {@link #delegate} option is used, these events fire only when a delegate element is entered of left.</b>.
84761      */
84762
84763     /**
84764      * @cfg {Ext.util.Region/Element} constrainTo
84765      * <p>A {@link Ext.util.Region Region} (Or an element from which a Region measurement will be read) which is used to constrain
84766      * the result of the {@link #getOffset} call.</p>
84767      * <p>This may be set any time during the DragTracker's lifecycle to set a dynamic constraining region.</p>
84768      */
84769
84770     /**
84771      * @cfg {Number} tolerance
84772      * Number of pixels the drag target must be moved before dragging is
84773      * considered to have started. Defaults to <code>5</code>.
84774      */
84775     tolerance: 5,
84776
84777     /**
84778      * @cfg {Boolean/Number} autoStart
84779      * Defaults to <code>false</code>. Specify <code>true</code> to defer trigger start by 1000 ms.
84780      * Specify a Number for the number of milliseconds to defer trigger start.
84781      */
84782     autoStart: false,
84783
84784     /**
84785      * @cfg {String} delegate
84786      * Optional. <p>A {@link Ext.DomQuery DomQuery} selector which identifies child elements within the DragTracker's encapsulating
84787      * Element which are the tracked elements. This limits tracking to only begin when the matching elements are mousedowned.</p>
84788      * <p>This may also be a specific child element within the DragTracker's encapsulating element to use as the tracked element.</p>
84789      */
84790
84791     /**
84792      * @cfg {Boolean} preventDefault
84793      * Specify <code>false</code> to enable default actions on onMouseDown events. Defaults to <code>true</code>.
84794      */
84795
84796     /**
84797      * @cfg {Boolean} stopEvent
84798      * 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>.
84799      */
84800
84801     constructor : function(config){
84802         Ext.apply(this, config);
84803         this.addEvents(
84804             /**
84805              * @event mouseover <p><b>Only available when {@link #trackOver} is <code>true</code></b></p>
84806              * <p>Fires when the mouse enters the DragTracker's target element (or if {@link #delegate} is
84807              * used, when the mouse enters a delegate element).</p>
84808              * @param {Object} this
84809              * @param {Object} e event object
84810              * @param {HtmlElement} target The element mouseovered.
84811              */
84812             'mouseover',
84813
84814             /**
84815              * @event mouseout <p><b>Only available when {@link #trackOver} is <code>true</code></b></p>
84816              * <p>Fires when the mouse exits the DragTracker's target element (or if {@link #delegate} is
84817              * used, when the mouse exits a delegate element).</p>
84818              * @param {Object} this
84819              * @param {Object} e event object
84820              */
84821             'mouseout',
84822
84823             /**
84824              * @event mousedown <p>Fires when the mouse button is pressed down, but before a drag operation begins. The
84825              * drag operation begins after either the mouse has been moved by {@link #tolerance} pixels, or after
84826              * the {@link #autoStart} timer fires.</p>
84827              * <p>Return false to veto the drag operation.</p>
84828              * @param {Object} this
84829              * @param {Object} e event object
84830              */
84831             'mousedown',
84832
84833             /**
84834              * @event mouseup
84835              * @param {Object} this
84836              * @param {Object} e event object
84837              */
84838             'mouseup',
84839
84840             /**
84841              * @event mousemove Fired when the mouse is moved. Returning false cancels the drag operation.
84842              * @param {Object} this
84843              * @param {Object} e event object
84844              */
84845             'mousemove',
84846
84847             /**
84848              * @event dragstart
84849              * @param {Object} this
84850              * @param {Object} e event object
84851              */
84852             'dragstart',
84853
84854             /**
84855              * @event dragend
84856              * @param {Object} this
84857              * @param {Object} e event object
84858              */
84859             'dragend',
84860
84861             /**
84862              * @event drag
84863              * @param {Object} this
84864              * @param {Object} e event object
84865              */
84866             'drag'
84867         );
84868
84869         this.dragRegion = Ext.create('Ext.util.Region', 0,0,0,0);
84870
84871         if (this.el) {
84872             this.initEl(this.el);
84873         }
84874
84875         // Dont pass the config so that it is not applied to 'this' again
84876         this.mixins.observable.constructor.call(this);
84877         if (this.disabled) {
84878             this.disable();
84879         }
84880
84881     },
84882
84883     /**
84884      * Initializes the DragTracker on a given element.
84885      * @param {Ext.core.Element/HTMLElement} el The element
84886      */
84887     initEl: function(el) {
84888         this.el = Ext.get(el);
84889
84890         // The delegate option may also be an element on which to listen
84891         this.handle = Ext.get(this.delegate);
84892
84893         // If delegate specified an actual element to listen on, we do not use the delegate listener option
84894         this.delegate = this.handle ? undefined : this.delegate;
84895
84896         if (!this.handle) {
84897             this.handle = this.el;
84898         }
84899
84900         // Add a mousedown listener which reacts only on the elements targeted by the delegate config.
84901         // We process mousedown to begin tracking.
84902         this.mon(this.handle, {
84903             mousedown: this.onMouseDown,
84904             delegate: this.delegate,
84905             scope: this
84906         });
84907
84908         // If configured to do so, track mouse entry and exit into the target (or delegate).
84909         // The mouseover and mouseout CANNOT be replaced with mouseenter and mouseleave
84910         // because delegate cannot work with those pseudoevents. Entry/exit checking is done in the handler.
84911         if (this.trackOver || this.overCls) {
84912             this.mon(this.handle, {
84913                 mouseover: this.onMouseOver,
84914                 mouseout: this.onMouseOut,
84915                 delegate: this.delegate,
84916                 scope: this
84917             });
84918         }
84919     },
84920
84921     disable: function() {
84922         this.disabled = true;
84923     },
84924
84925     enable: function() {
84926         this.disabled = false;
84927     },
84928
84929     destroy : function() {
84930         this.clearListeners();
84931         delete this.el;
84932     },
84933
84934     // When the pointer enters a tracking element, fire a mouseover if the mouse entered from outside.
84935     // This is mouseenter functionality, but we cannot use mouseenter because we are using "delegate" to filter mouse targets
84936     onMouseOver: function(e, target) {
84937         var me = this;
84938         if (!me.disabled) {
84939             if (Ext.EventManager.contains(e) || me.delegate) {
84940                 me.mouseIsOut = false;
84941                 if (me.overCls) {
84942                     me.el.addCls(me.overCls);
84943                 }
84944                 me.fireEvent('mouseover', me, e, me.delegate ? e.getTarget(me.delegate, target) : me.handle);
84945             }
84946         }
84947     },
84948
84949     // When the pointer exits a tracking element, fire a mouseout.
84950     // This is mouseleave functionality, but we cannot use mouseleave because we are using "delegate" to filter mouse targets
84951     onMouseOut: function(e) {
84952         if (this.mouseIsDown) {
84953             this.mouseIsOut = true;
84954         } else {
84955             if (this.overCls) {
84956                 this.el.removeCls(this.overCls);
84957             }
84958             this.fireEvent('mouseout', this, e);
84959         }
84960     },
84961
84962     onMouseDown: function(e, target){
84963         // If this is disabled, or the mousedown has been processed by an upstream DragTracker, return
84964         if (this.disabled ||e.dragTracked) {
84965             return;
84966         }
84967
84968         // This information should be available in mousedown listener and onBeforeStart implementations
84969         this.dragTarget = this.delegate ? target : this.handle.dom;
84970         this.startXY = this.lastXY = e.getXY();
84971         this.startRegion = Ext.fly(this.dragTarget).getRegion();
84972
84973         if (this.fireEvent('mousedown', this, e) !== false && this.onBeforeStart(e) !== false) {
84974
84975             // Track when the mouse is down so that mouseouts while the mouse is down are not processed.
84976             // The onMouseOut method will only ever be called after mouseup.
84977             this.mouseIsDown = true;
84978
84979             // Flag for downstream DragTracker instances that the mouse is being tracked.
84980             e.dragTracked = true;
84981
84982             if (this.preventDefault !== false) {
84983                 e.preventDefault();
84984             }
84985             Ext.getDoc().on({
84986                 scope: this,
84987                 mouseup: this.onMouseUp,
84988                 mousemove: this.onMouseMove,
84989                 selectstart: this.stopSelect
84990             });
84991             if (this.autoStart) {
84992                 this.timer =  Ext.defer(this.triggerStart, this.autoStart === true ? 1000 : this.autoStart, this, [e]);
84993             }
84994         }
84995     },
84996
84997     onMouseMove: function(e, target){
84998         // BrowserBug: IE hack to see if button was released outside of window.
84999         // Needed in IE6-9 in quirks and strictmode
85000         if (this.active && Ext.isIE && !e.browserEvent.button) {
85001             e.preventDefault();
85002             this.onMouseUp(e);
85003             return;
85004         }
85005
85006         e.preventDefault();
85007         var xy = e.getXY(),
85008             s = this.startXY;
85009
85010         this.lastXY = xy;
85011         if (!this.active) {
85012             if (Math.max(Math.abs(s[0]-xy[0]), Math.abs(s[1]-xy[1])) > this.tolerance) {
85013                 this.triggerStart(e);
85014             } else {
85015                 return;
85016             }
85017         }
85018
85019         // Returning false from a mousemove listener deactivates 
85020         if (this.fireEvent('mousemove', this, e) === false) {
85021             this.onMouseUp(e);
85022         } else {
85023             this.onDrag(e);
85024             this.fireEvent('drag', this, e);
85025         }
85026     },
85027
85028     onMouseUp: function(e) {
85029         // Clear the flag which ensures onMouseOut fires only after the mouse button
85030         // is lifted if the mouseout happens *during* a drag.
85031         this.mouseIsDown = false;
85032
85033         // Remove flag from event singleton
85034         delete e.dragTracked;
85035
85036         // If we mouseouted the el *during* the drag, the onMouseOut method will not have fired. Ensure that it gets processed.
85037         if (this.mouseIsOut) {
85038             this.mouseIsOut = false;
85039             this.onMouseOut(e);
85040         }
85041         e.preventDefault();
85042         this.fireEvent('mouseup', this, e);
85043         this.endDrag(e);
85044     },
85045
85046     /**
85047      * @private
85048      * Stop the drag operation, and remove active mouse listeners.
85049      */
85050     endDrag: function(e) {
85051         var doc = Ext.getDoc(),
85052         wasActive = this.active;
85053
85054         doc.un('mousemove', this.onMouseMove, this);
85055         doc.un('mouseup', this.onMouseUp, this);
85056         doc.un('selectstart', this.stopSelect, this);
85057         this.clearStart();
85058         this.active = false;
85059         if (wasActive) {
85060             this.onEnd(e);
85061             this.fireEvent('dragend', this, e);
85062         }
85063         // Private property calculated when first required and only cached during a drag
85064         delete this._constrainRegion;
85065     },
85066
85067     triggerStart: function(e) {
85068         this.clearStart();
85069         this.active = true;
85070         this.onStart(e);
85071         this.fireEvent('dragstart', this, e);
85072     },
85073
85074     clearStart : function() {
85075         if (this.timer) {
85076             clearTimeout(this.timer);
85077             delete this.timer;
85078         }
85079     },
85080
85081     stopSelect : function(e) {
85082         e.stopEvent();
85083         return false;
85084     },
85085
85086     /**
85087      * Template method which should be overridden by each DragTracker instance. Called when the user first clicks and
85088      * holds the mouse button down. Return false to disallow the drag
85089      * @param {Ext.EventObject} e The event object
85090      */
85091     onBeforeStart : function(e) {
85092
85093     },
85094
85095     /**
85096      * Template method which should be overridden by each DragTracker instance. Called when a drag operation starts
85097      * (e.g. the user has moved the tracked element beyond the specified tolerance)
85098      * @param {Ext.EventObject} e The event object
85099      */
85100     onStart : function(xy) {
85101
85102     },
85103
85104     /**
85105      * Template method which should be overridden by each DragTracker instance. Called whenever a drag has been detected.
85106      * @param {Ext.EventObject} e The event object
85107      */
85108     onDrag : function(e) {
85109
85110     },
85111
85112     /**
85113      * Template method which should be overridden by each DragTracker instance. Called when a drag operation has been completed
85114      * (e.g. the user clicked and held the mouse down, dragged the element and then released the mouse button)
85115      * @param {Ext.EventObject} e The event object
85116      */
85117     onEnd : function(e) {
85118
85119     },
85120
85121     /**
85122      * </p>Returns the drag target. This is usually the DragTracker's encapsulating element.</p>
85123      * <p>If the {@link #delegate} option is being used, this may be a child element which matches the
85124      * {@link #delegate} selector.</p>
85125      * @return {Ext.core.Element} The element currently being tracked.
85126      */
85127     getDragTarget : function(){
85128         return this.dragTarget;
85129     },
85130
85131     /**
85132      * @private
85133      * @returns {Element} The DragTracker's encapsulating element.
85134      */
85135     getDragCt : function(){
85136         return this.el;
85137     },
85138
85139     /**
85140      * @private
85141      * Return the Region into which the drag operation is constrained.
85142      * Either the XY pointer itself can be constrained, or the dragTarget element
85143      * The private property _constrainRegion is cached until onMouseUp
85144      */
85145     getConstrainRegion: function() {
85146         if (this.constrainTo) {
85147             if (this.constrainTo instanceof Ext.util.Region) {
85148                 return this.constrainTo;
85149             }
85150             if (!this._constrainRegion) {
85151                 this._constrainRegion = Ext.fly(this.constrainTo).getViewRegion();
85152             }
85153         } else {
85154             if (!this._constrainRegion) {
85155                 this._constrainRegion = this.getDragCt().getViewRegion();
85156             }
85157         }
85158         return this._constrainRegion;
85159     },
85160
85161     getXY : function(constrain){
85162         return constrain ? this.constrainModes[constrain].call(this, this.lastXY) : this.lastXY;
85163     },
85164
85165     /**
85166      * <p>Returns the X, Y offset of the current mouse position from the mousedown point.</p>
85167      * <p>This method may optionally constrain the real offset values, and returns a point coerced in one
85168      * of two modes:</p><ul>
85169      * <li><code>point</code><div class="sub-desc">The current mouse position is coerced into the
85170      * {@link #constrainRegion}, and the resulting position is returned.</div></li>
85171      * <li><code>dragTarget</code><div class="sub-desc">The new {@link Ext.util.Region Region} of the
85172      * {@link #getDragTarget dragTarget} is calculated based upon the current mouse position, and then
85173      * coerced into the {@link #constrainRegion}. The returned mouse position is then adjusted by the
85174      * same delta as was used to coerce the region.</div></li>
85175      * </ul>
85176      * @param constrainMode {String} Optional. If omitted the true mouse position is returned. May be passed
85177      * as <code>'point'</code> or <code>'dragTarget'. See above.</code>.
85178      * @returns {Array} The <code>X, Y</code> offset from the mousedown point, optionally constrained.
85179      */
85180     getOffset : function(constrain){
85181         var xy = this.getXY(constrain),
85182             s = this.startXY;
85183
85184         return [xy[0]-s[0], xy[1]-s[1]];
85185     },
85186
85187     constrainModes: {
85188         // Constrain the passed point to within the constrain region
85189         point: function(xy) {
85190             var dr = this.dragRegion,
85191                 constrainTo = this.getConstrainRegion();
85192
85193             // No constraint
85194             if (!constrainTo) {
85195                 return xy;
85196             }
85197
85198             dr.x = dr.left = dr[0] = dr.right = xy[0];
85199             dr.y = dr.top = dr[1] = dr.bottom = xy[1];
85200             dr.constrainTo(constrainTo);
85201
85202             return [dr.left, dr.top];
85203         },
85204
85205         // Constrain the dragTarget to within the constrain region. Return the passed xy adjusted by the same delta.
85206         dragTarget: function(xy) {
85207             var s = this.startXY,
85208                 dr = this.startRegion.copy(),
85209                 constrainTo = this.getConstrainRegion(),
85210                 adjust;
85211
85212             // No constraint
85213             if (!constrainTo) {
85214                 return xy;
85215             }
85216
85217             // See where the passed XY would put the dragTarget if translated by the unconstrained offset.
85218             // If it overflows, we constrain the passed XY to bring the potential
85219             // region back within the boundary.
85220             dr.translateBy.apply(dr, [xy[0]-s[0], xy[1]-s[1]]);
85221
85222             // Constrain the X coordinate by however much the dragTarget overflows
85223             if (dr.right > constrainTo.right) {
85224                 xy[0] += adjust = (constrainTo.right - dr.right);    // overflowed the right
85225                 dr.left += adjust;
85226             }
85227             if (dr.left < constrainTo.left) {
85228                 xy[0] += (constrainTo.left - dr.left);      // overflowed the left
85229             }
85230
85231             // Constrain the X coordinate by however much the dragTarget overflows
85232             if (dr.bottom > constrainTo.bottom) {
85233                 xy[1] += adjust = (constrainTo.bottom - dr.bottom);  // overflowed the bottom
85234                 dr.top += adjust;
85235             }
85236             if (dr.top < constrainTo.top) {
85237                 xy[1] += (constrainTo.top - dr.top);        // overflowed the top
85238             }
85239             return xy;
85240         }
85241     }
85242 });
85243 /**
85244  * @class Ext.dd.DragZone
85245  * @extends Ext.dd.DragSource
85246  * <p>This class provides a container DD instance that allows dragging of multiple child source nodes.</p>
85247  * <p>This class does not move the drag target nodes, but a proxy element which may contain
85248  * any DOM structure you wish. The DOM element to show in the proxy is provided by either a
85249  * provided implementation of {@link #getDragData}, or by registered draggables registered with {@link Ext.dd.Registry}</p>
85250  * <p>If you wish to provide draggability for an arbitrary number of DOM nodes, each of which represent some
85251  * application object (For example nodes in a {@link Ext.view.View DataView}) then use of this class
85252  * is the most efficient way to "activate" those nodes.</p>
85253  * <p>By default, this class requires that draggable child nodes are registered with {@link Ext.dd.Registry}.
85254  * However a simpler way to allow a DragZone to manage any number of draggable elements is to configure
85255  * the DragZone with  an implementation of the {@link #getDragData} method which interrogates the passed
85256  * mouse event to see if it has taken place within an element, or class of elements. This is easily done
85257  * by using the event's {@link Ext.EventObject#getTarget getTarget} method to identify a node based on a
85258  * {@link Ext.DomQuery} selector. For example, to make the nodes of a DataView draggable, use the following
85259  * technique. Knowledge of the use of the DataView is required:</p><pre><code>
85260 myDataView.on('render', function(v) {
85261     myDataView.dragZone = new Ext.dd.DragZone(v.getEl(), {
85262
85263 //      On receipt of a mousedown event, see if it is within a DataView node.
85264 //      Return a drag data object if so.
85265         getDragData: function(e) {
85266
85267 //          Use the DataView's own itemSelector (a mandatory property) to
85268 //          test if the mousedown is within one of the DataView's nodes.
85269             var sourceEl = e.getTarget(v.itemSelector, 10);
85270
85271 //          If the mousedown is within a DataView node, clone the node to produce
85272 //          a ddel element for use by the drag proxy. Also add application data
85273 //          to the returned data object.
85274             if (sourceEl) {
85275                 d = sourceEl.cloneNode(true);
85276                 d.id = Ext.id();
85277                 return {
85278                     ddel: d,
85279                     sourceEl: sourceEl,
85280                     repairXY: Ext.fly(sourceEl).getXY(),
85281                     sourceStore: v.store,
85282                     draggedRecord: v.{@link Ext.view.View#getRecord getRecord}(sourceEl)
85283                 }
85284             }
85285         },
85286
85287 //      Provide coordinates for the proxy to slide back to on failed drag.
85288 //      This is the original XY coordinates of the draggable element captured
85289 //      in the getDragData method.
85290         getRepairXY: function() {
85291             return this.dragData.repairXY;
85292         }
85293     });
85294 });</code></pre>
85295  * See the {@link Ext.dd.DropZone DropZone} documentation for details about building a DropZone which
85296  * cooperates with this DragZone.
85297  * @constructor
85298  * @param {Mixed} el The container element
85299  * @param {Object} config
85300  */
85301 Ext.define('Ext.dd.DragZone', {
85302
85303     extend: 'Ext.dd.DragSource',
85304
85305     constructor : function(el, config){
85306         this.callParent([el, config]);
85307         if (this.containerScroll) {
85308             Ext.dd.ScrollManager.register(this.el);
85309         }
85310     },
85311
85312     /**
85313      * This property contains the data representing the dragged object. This data is set up by the implementation
85314      * of the {@link #getDragData} method. It must contain a <tt>ddel</tt> property, but can contain
85315      * any other data according to the application's needs.
85316      * @type Object
85317      * @property dragData
85318      */
85319
85320     /**
85321      * @cfg {Boolean} containerScroll True to register this container with the Scrollmanager
85322      * for auto scrolling during drag operations.
85323      */
85324
85325     /**
85326      * Called when a mousedown occurs in this container. Looks in {@link Ext.dd.Registry}
85327      * for a valid target to drag based on the mouse down. Override this method
85328      * to provide your own lookup logic (e.g. finding a child by class name). Make sure your returned
85329      * object has a "ddel" attribute (with an HTML Element) for other functions to work.
85330      * @param {EventObject} e The mouse down event
85331      * @return {Object} The dragData
85332      */
85333     getDragData : function(e){
85334         return Ext.dd.Registry.getHandleFromEvent(e);
85335     },
85336
85337     /**
85338      * Called once drag threshold has been reached to initialize the proxy element. By default, it clones the
85339      * this.dragData.ddel
85340      * @param {Number} x The x position of the click on the dragged object
85341      * @param {Number} y The y position of the click on the dragged object
85342      * @return {Boolean} true to continue the drag, false to cancel
85343      */
85344     onInitDrag : function(x, y){
85345         this.proxy.update(this.dragData.ddel.cloneNode(true));
85346         this.onStartDrag(x, y);
85347         return true;
85348     },
85349
85350     /**
85351      * Called after a repair of an invalid drop. By default, highlights this.dragData.ddel
85352      */
85353     afterRepair : function(){
85354         var me = this;
85355         if (Ext.enableFx) {
85356             Ext.fly(me.dragData.ddel).highlight(me.repairHighlightColor);
85357         }
85358         me.dragging = false;
85359     },
85360
85361     /**
85362      * Called before a repair of an invalid drop to get the XY to animate to. By default returns
85363      * the XY of this.dragData.ddel
85364      * @param {EventObject} e The mouse up event
85365      * @return {Array} The xy location (e.g. [100, 200])
85366      */
85367     getRepairXY : function(e){
85368         return Ext.core.Element.fly(this.dragData.ddel).getXY();
85369     },
85370
85371     destroy : function(){
85372         this.callParent();
85373         if (this.containerScroll) {
85374             Ext.dd.ScrollManager.unregister(this.el);
85375         }
85376     }
85377 });
85378
85379 /**
85380  * @class Ext.dd.ScrollManager
85381  * <p>Provides automatic scrolling of overflow regions in the page during drag operations.</p>
85382  * <p>The ScrollManager configs will be used as the defaults for any scroll container registered with it,
85383  * but you can also override most of the configs per scroll container by adding a
85384  * <tt>ddScrollConfig</tt> object to the target element that contains these properties: {@link #hthresh},
85385  * {@link #vthresh}, {@link #increment} and {@link #frequency}.  Example usage:
85386  * <pre><code>
85387 var el = Ext.get('scroll-ct');
85388 el.ddScrollConfig = {
85389     vthresh: 50,
85390     hthresh: -1,
85391     frequency: 100,
85392     increment: 200
85393 };
85394 Ext.dd.ScrollManager.register(el);
85395 </code></pre>
85396  * <b>Note: This class uses "Point Mode" and is untested in "Intersect Mode".</b>
85397  * @singleton
85398  */
85399 Ext.define('Ext.dd.ScrollManager', {
85400     singleton: true,
85401     requires: [
85402         'Ext.dd.DragDropManager'
85403     ],
85404
85405     constructor: function() {
85406         var ddm = Ext.dd.DragDropManager;
85407         ddm.fireEvents = Ext.Function.createSequence(ddm.fireEvents, this.onFire, this);
85408         ddm.stopDrag = Ext.Function.createSequence(ddm.stopDrag, this.onStop, this);
85409         this.doScroll = Ext.Function.bind(this.doScroll, this);
85410         this.ddmInstance = ddm;
85411         this.els = {};
85412         this.dragEl = null;
85413         this.proc = {};
85414     },
85415
85416     onStop: function(e){
85417         var sm = Ext.dd.ScrollManager;
85418         sm.dragEl = null;
85419         sm.clearProc();
85420     },
85421
85422     triggerRefresh: function() {
85423         if (this.ddmInstance.dragCurrent) {
85424             this.ddmInstance.refreshCache(this.ddmInstance.dragCurrent.groups);
85425         }
85426     },
85427
85428     doScroll: function() {
85429         if (this.ddmInstance.dragCurrent) {
85430             var proc   = this.proc,
85431                 procEl = proc.el,
85432                 ddScrollConfig = proc.el.ddScrollConfig,
85433                 inc = ddScrollConfig ? ddScrollConfig.increment : this.increment;
85434
85435             if (!this.animate) {
85436                 if (procEl.scroll(proc.dir, inc)) {
85437                     this.triggerRefresh();
85438                 }
85439             } else {
85440                 procEl.scroll(proc.dir, inc, true, this.animDuration, this.triggerRefresh);
85441             }
85442         }
85443     },
85444
85445     clearProc: function() {
85446         var proc = this.proc;
85447         if (proc.id) {
85448             clearInterval(proc.id);
85449         }
85450         proc.id = 0;
85451         proc.el = null;
85452         proc.dir = "";
85453     },
85454
85455     startProc: function(el, dir) {
85456         this.clearProc();
85457         this.proc.el = el;
85458         this.proc.dir = dir;
85459         var group = el.ddScrollConfig ? el.ddScrollConfig.ddGroup : undefined,
85460             freq  = (el.ddScrollConfig && el.ddScrollConfig.frequency)
85461                   ? el.ddScrollConfig.frequency
85462                   : this.frequency;
85463
85464         if (group === undefined || this.ddmInstance.dragCurrent.ddGroup == group) {
85465             this.proc.id = setInterval(this.doScroll, freq);
85466         }
85467     },
85468
85469     onFire: function(e, isDrop) {
85470         if (isDrop || !this.ddmInstance.dragCurrent) {
85471             return;
85472         }
85473         if (!this.dragEl || this.dragEl != this.ddmInstance.dragCurrent) {
85474             this.dragEl = this.ddmInstance.dragCurrent;
85475             // refresh regions on drag start
85476             this.refreshCache();
85477         }
85478
85479         var xy = e.getXY(),
85480             pt = e.getPoint(),
85481             proc = this.proc,
85482             els = this.els;
85483
85484         for (var id in els) {
85485             var el = els[id], r = el._region;
85486             var c = el.ddScrollConfig ? el.ddScrollConfig : this;
85487             if (r && r.contains(pt) && el.isScrollable()) {
85488                 if (r.bottom - pt.y <= c.vthresh) {
85489                     if(proc.el != el){
85490                         this.startProc(el, "down");
85491                     }
85492                     return;
85493                 }else if (r.right - pt.x <= c.hthresh) {
85494                     if (proc.el != el) {
85495                         this.startProc(el, "left");
85496                     }
85497                     return;
85498                 } else if(pt.y - r.top <= c.vthresh) {
85499                     if (proc.el != el) {
85500                         this.startProc(el, "up");
85501                     }
85502                     return;
85503                 } else if(pt.x - r.left <= c.hthresh) {
85504                     if (proc.el != el) {
85505                         this.startProc(el, "right");
85506                     }
85507                     return;
85508                 }
85509             }
85510         }
85511         this.clearProc();
85512     },
85513
85514     /**
85515      * Registers new overflow element(s) to auto scroll
85516      * @param {Mixed/Array} el The id of or the element to be scrolled or an array of either
85517      */
85518     register : function(el){
85519         if (Ext.isArray(el)) {
85520             for(var i = 0, len = el.length; i < len; i++) {
85521                     this.register(el[i]);
85522             }
85523         } else {
85524             el = Ext.get(el);
85525             this.els[el.id] = el;
85526         }
85527     },
85528
85529     /**
85530      * Unregisters overflow element(s) so they are no longer scrolled
85531      * @param {Mixed/Array} el The id of or the element to be removed or an array of either
85532      */
85533     unregister : function(el){
85534         if(Ext.isArray(el)){
85535             for (var i = 0, len = el.length; i < len; i++) {
85536                 this.unregister(el[i]);
85537             }
85538         }else{
85539             el = Ext.get(el);
85540             delete this.els[el.id];
85541         }
85542     },
85543
85544     /**
85545      * The number of pixels from the top or bottom edge of a container the pointer needs to be to
85546      * trigger scrolling (defaults to 25)
85547      * @type Number
85548      */
85549     vthresh : 25,
85550     /**
85551      * The number of pixels from the right or left edge of a container the pointer needs to be to
85552      * trigger scrolling (defaults to 25)
85553      * @type Number
85554      */
85555     hthresh : 25,
85556
85557     /**
85558      * The number of pixels to scroll in each scroll increment (defaults to 100)
85559      * @type Number
85560      */
85561     increment : 100,
85562
85563     /**
85564      * The frequency of scrolls in milliseconds (defaults to 500)
85565      * @type Number
85566      */
85567     frequency : 500,
85568
85569     /**
85570      * True to animate the scroll (defaults to true)
85571      * @type Boolean
85572      */
85573     animate: true,
85574
85575     /**
85576      * The animation duration in seconds -
85577      * MUST BE less than Ext.dd.ScrollManager.frequency! (defaults to .4)
85578      * @type Number
85579      */
85580     animDuration: 0.4,
85581
85582     /**
85583      * The named drag drop {@link Ext.dd.DragSource#ddGroup group} to which this container belongs (defaults to undefined).
85584      * If a ddGroup is specified, then container scrolling will only occur when a dragged object is in the same ddGroup.
85585      * @type String
85586      */
85587     ddGroup: undefined,
85588
85589     /**
85590      * Manually trigger a cache refresh.
85591      */
85592     refreshCache : function(){
85593         var els = this.els,
85594             id;
85595         for (id in els) {
85596             if(typeof els[id] == 'object'){ // for people extending the object prototype
85597                 els[id]._region = els[id].getRegion();
85598             }
85599         }
85600     }
85601 });
85602
85603 /**
85604  * @class Ext.dd.DropTarget
85605  * @extends Ext.dd.DDTarget
85606  * A simple class that provides the basic implementation needed to make any element a drop target that can have
85607  * draggable items dropped onto it.  The drop has no effect until an implementation of notifyDrop is provided.
85608  * @constructor
85609  * @param {Mixed} el The container element
85610  * @param {Object} config
85611  */
85612 Ext.define('Ext.dd.DropTarget', {
85613     extend: 'Ext.dd.DDTarget',
85614     requires: ['Ext.dd.ScrollManager'],
85615
85616     constructor : function(el, config){
85617         this.el = Ext.get(el);
85618
85619         Ext.apply(this, config);
85620
85621         if(this.containerScroll){
85622             Ext.dd.ScrollManager.register(this.el);
85623         }
85624
85625         this.callParent([this.el.dom, this.ddGroup || this.group,
85626               {isTarget: true}]);
85627     },
85628
85629     /**
85630      * @cfg {String} ddGroup
85631      * A named drag drop group to which this object belongs.  If a group is specified, then this object will only
85632      * interact with other drag drop objects in the same group (defaults to undefined).
85633      */
85634     /**
85635      * @cfg {String} overClass
85636      * The CSS class applied to the drop target element while the drag source is over it (defaults to "").
85637      */
85638     /**
85639      * @cfg {String} dropAllowed
85640      * The CSS class returned to the drag source when drop is allowed (defaults to "x-dd-drop-ok").
85641      */
85642     dropAllowed : Ext.baseCSSPrefix + 'dd-drop-ok',
85643     /**
85644      * @cfg {String} dropNotAllowed
85645      * The CSS class returned to the drag source when drop is not allowed (defaults to "x-dd-drop-nodrop").
85646      */
85647     dropNotAllowed : Ext.baseCSSPrefix + 'dd-drop-nodrop',
85648
85649     // private
85650     isTarget : true,
85651
85652     // private
85653     isNotifyTarget : true,
85654
85655     /**
85656      * The function a {@link Ext.dd.DragSource} calls once to notify this drop target that the source is now over the
85657      * target.  This default implementation adds the CSS class specified by overClass (if any) to the drop element
85658      * and returns the dropAllowed config value.  This method should be overridden if drop validation is required.
85659      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop target
85660      * @param {Event} e The event
85661      * @param {Object} data An object containing arbitrary data supplied by the drag source
85662      * @return {String} status The CSS class that communicates the drop status back to the source so that the
85663      * underlying {@link Ext.dd.StatusProxy} can be updated
85664      */
85665     notifyEnter : function(dd, e, data){
85666         if(this.overClass){
85667             this.el.addCls(this.overClass);
85668         }
85669         return this.dropAllowed;
85670     },
85671
85672     /**
85673      * The function a {@link Ext.dd.DragSource} calls continuously while it is being dragged over the target.
85674      * This method will be called on every mouse movement while the drag source is over the drop target.
85675      * This default implementation simply returns the dropAllowed config value.
85676      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop target
85677      * @param {Event} e The event
85678      * @param {Object} data An object containing arbitrary data supplied by the drag source
85679      * @return {String} status The CSS class that communicates the drop status back to the source so that the
85680      * underlying {@link Ext.dd.StatusProxy} can be updated
85681      */
85682     notifyOver : function(dd, e, data){
85683         return this.dropAllowed;
85684     },
85685
85686     /**
85687      * The function a {@link Ext.dd.DragSource} calls once to notify this drop target that the source has been dragged
85688      * out of the target without dropping.  This default implementation simply removes the CSS class specified by
85689      * overClass (if any) from the drop element.
85690      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop target
85691      * @param {Event} e The event
85692      * @param {Object} data An object containing arbitrary data supplied by the drag source
85693      */
85694     notifyOut : function(dd, e, data){
85695         if(this.overClass){
85696             this.el.removeCls(this.overClass);
85697         }
85698     },
85699
85700     /**
85701      * The function a {@link Ext.dd.DragSource} calls once to notify this drop target that the dragged item has
85702      * been dropped on it.  This method has no default implementation and returns false, so you must provide an
85703      * implementation that does something to process the drop event and returns true so that the drag source's
85704      * repair action does not run.
85705      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop target
85706      * @param {Event} e The event
85707      * @param {Object} data An object containing arbitrary data supplied by the drag source
85708      * @return {Boolean} False if the drop was invalid.
85709      */
85710     notifyDrop : function(dd, e, data){
85711         return false;
85712     },
85713
85714     destroy : function(){
85715         this.callParent();
85716         if(this.containerScroll){
85717             Ext.dd.ScrollManager.unregister(this.el);
85718         }
85719     }
85720 });
85721
85722 /**
85723  * @class Ext.dd.Registry
85724  * Provides easy access to all drag drop components that are registered on a page.  Items can be retrieved either
85725  * directly by DOM node id, or by passing in the drag drop event that occurred and looking up the event target.
85726  * @singleton
85727  */
85728 Ext.define('Ext.dd.Registry', {
85729     singleton: true,
85730     constructor: function() {
85731         this.elements = {}; 
85732         this.handles = {}; 
85733         this.autoIdSeed = 0;
85734     },
85735     
85736     getId: function(el, autogen){
85737         if(typeof el == "string"){
85738             return el;
85739         }
85740         var id = el.id;
85741         if(!id && autogen !== false){
85742             id = "extdd-" + (++this.autoIdSeed);
85743             el.id = id;
85744         }
85745         return id;
85746     },
85747     
85748     /**
85749      * Resgister a drag drop element
85750      * @param {String/HTMLElement} element The id or DOM node to register
85751      * @param {Object} data (optional) An custom data object that will be passed between the elements that are involved
85752      * in drag drop operations.  You can populate this object with any arbitrary properties that your own code
85753      * knows how to interpret, plus there are some specific properties known to the Registry that should be
85754      * populated in the data object (if applicable):
85755      * <pre>
85756 Value      Description<br />
85757 ---------  ------------------------------------------<br />
85758 handles    Array of DOM nodes that trigger dragging<br />
85759            for the element being registered<br />
85760 isHandle   True if the element passed in triggers<br />
85761            dragging itself, else false
85762 </pre>
85763      */
85764     register : function(el, data){
85765         data = data || {};
85766         if (typeof el == "string") {
85767             el = document.getElementById(el);
85768         }
85769         data.ddel = el;
85770         this.elements[this.getId(el)] = data;
85771         if (data.isHandle !== false) {
85772             this.handles[data.ddel.id] = data;
85773         }
85774         if (data.handles) {
85775             var hs = data.handles;
85776             for (var i = 0, len = hs.length; i < len; i++) {
85777                 this.handles[this.getId(hs[i])] = data;
85778             }
85779         }
85780     },
85781
85782     /**
85783      * Unregister a drag drop element
85784      * @param {String/HTMLElement} element The id or DOM node to unregister
85785      */
85786     unregister : function(el){
85787         var id = this.getId(el, false);
85788         var data = this.elements[id];
85789         if(data){
85790             delete this.elements[id];
85791             if(data.handles){
85792                 var hs = data.handles;
85793                 for (var i = 0, len = hs.length; i < len; i++) {
85794                     delete this.handles[this.getId(hs[i], false)];
85795                 }
85796             }
85797         }
85798     },
85799
85800     /**
85801      * Returns the handle registered for a DOM Node by id
85802      * @param {String/HTMLElement} id The DOM node or id to look up
85803      * @return {Object} handle The custom handle data
85804      */
85805     getHandle : function(id){
85806         if(typeof id != "string"){ // must be element?
85807             id = id.id;
85808         }
85809         return this.handles[id];
85810     },
85811
85812     /**
85813      * Returns the handle that is registered for the DOM node that is the target of the event
85814      * @param {Event} e The event
85815      * @return {Object} handle The custom handle data
85816      */
85817     getHandleFromEvent : function(e){
85818         var t = e.getTarget();
85819         return t ? this.handles[t.id] : null;
85820     },
85821
85822     /**
85823      * Returns a custom data object that is registered for a DOM node by id
85824      * @param {String/HTMLElement} id The DOM node or id to look up
85825      * @return {Object} data The custom data
85826      */
85827     getTarget : function(id){
85828         if(typeof id != "string"){ // must be element?
85829             id = id.id;
85830         }
85831         return this.elements[id];
85832     },
85833
85834     /**
85835      * Returns a custom data object that is registered for the DOM node that is the target of the event
85836      * @param {Event} e The event
85837      * @return {Object} data The custom data
85838      */
85839     getTargetFromEvent : function(e){
85840         var t = e.getTarget();
85841         return t ? this.elements[t.id] || this.handles[t.id] : null;
85842     }
85843 });
85844 /**
85845  * @class Ext.dd.DropZone
85846  * @extends Ext.dd.DropTarget
85847
85848 This class provides a container DD instance that allows dropping on multiple child target nodes.
85849
85850 By default, this class requires that child nodes accepting drop are registered with {@link Ext.dd.Registry}.
85851 However a simpler way to allow a DropZone to manage any number of target elements is to configure the
85852 DropZone with an implementation of {@link #getTargetFromEvent} which interrogates the passed
85853 mouse event to see if it has taken place within an element, or class of elements. This is easily done
85854 by using the event's {@link Ext.EventObject#getTarget getTarget} method to identify a node based on a
85855 {@link Ext.DomQuery} selector.
85856
85857 Once the DropZone has detected through calling getTargetFromEvent, that the mouse is over
85858 a drop target, that target is passed as the first parameter to {@link #onNodeEnter}, {@link #onNodeOver},
85859 {@link #onNodeOut}, {@link #onNodeDrop}. You may configure the instance of DropZone with implementations
85860 of these methods to provide application-specific behaviour for these events to update both
85861 application state, and UI state.
85862
85863 For example to make a GridPanel a cooperating target with the example illustrated in
85864 {@link Ext.dd.DragZone DragZone}, the following technique might be used:
85865
85866     myGridPanel.on('render', function() {
85867         myGridPanel.dropZone = new Ext.dd.DropZone(myGridPanel.getView().scroller, {
85868
85869             // If the mouse is over a grid row, return that node. This is
85870             // provided as the "target" parameter in all "onNodeXXXX" node event handling functions
85871             getTargetFromEvent: function(e) {
85872                 return e.getTarget(myGridPanel.getView().rowSelector);
85873             },
85874
85875             // On entry into a target node, highlight that node.
85876             onNodeEnter : function(target, dd, e, data){ 
85877                 Ext.fly(target).addCls('my-row-highlight-class');
85878             },
85879
85880             // On exit from a target node, unhighlight that node.
85881             onNodeOut : function(target, dd, e, data){ 
85882                 Ext.fly(target).removeCls('my-row-highlight-class');
85883             },
85884
85885             // While over a target node, return the default drop allowed class which
85886             // places a "tick" icon into the drag proxy.
85887             onNodeOver : function(target, dd, e, data){ 
85888                 return Ext.dd.DropZone.prototype.dropAllowed;
85889             },
85890
85891             // On node drop we can interrogate the target to find the underlying
85892             // application object that is the real target of the dragged data.
85893             // In this case, it is a Record in the GridPanel's Store.
85894             // We can use the data set up by the DragZone's getDragData method to read
85895             // any data we decided to attach in the DragZone's getDragData method.
85896             onNodeDrop : function(target, dd, e, data){
85897                 var rowIndex = myGridPanel.getView().findRowIndex(target);
85898                 var r = myGridPanel.getStore().getAt(rowIndex);
85899                 Ext.Msg.alert('Drop gesture', 'Dropped Record id ' + data.draggedRecord.id +
85900                     ' on Record id ' + r.id);
85901                 return true;
85902             }
85903         });
85904     }
85905
85906 See the {@link Ext.dd.DragZone DragZone} documentation for details about building a DragZone which
85907 cooperates with this DropZone.
85908
85909  * @constructor
85910  * @param {Mixed} el The container element
85911  * @param {Object} config
85912  * @markdown
85913  */
85914 Ext.define('Ext.dd.DropZone', {
85915     extend: 'Ext.dd.DropTarget',
85916     requires: ['Ext.dd.Registry'],
85917
85918     /**
85919      * Returns a custom data object associated with the DOM node that is the target of the event.  By default
85920      * this looks up the event target in the {@link Ext.dd.Registry}, although you can override this method to
85921      * provide your own custom lookup.
85922      * @param {Event} e The event
85923      * @return {Object} data The custom data
85924      */
85925     getTargetFromEvent : function(e){
85926         return Ext.dd.Registry.getTargetFromEvent(e);
85927     },
85928
85929     /**
85930      * Called when the DropZone determines that a {@link Ext.dd.DragSource} has entered a drop node
85931      * that has either been registered or detected by a configured implementation of {@link #getTargetFromEvent}.
85932      * This method has no default implementation and should be overridden to provide
85933      * node-specific processing if necessary.
85934      * @param {Object} nodeData The custom data associated with the drop node (this is the same value returned from 
85935      * {@link #getTargetFromEvent} for this node)
85936      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
85937      * @param {Event} e The event
85938      * @param {Object} data An object containing arbitrary data supplied by the drag source
85939      */
85940     onNodeEnter : function(n, dd, e, data){
85941         
85942     },
85943
85944     /**
85945      * Called while the DropZone determines that a {@link Ext.dd.DragSource} is over a drop node
85946      * that has either been registered or detected by a configured implementation of {@link #getTargetFromEvent}.
85947      * The default implementation returns this.dropNotAllowed, so it should be
85948      * overridden to provide the proper feedback.
85949      * @param {Object} nodeData The custom data associated with the drop node (this is the same value returned from
85950      * {@link #getTargetFromEvent} for this node)
85951      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
85952      * @param {Event} e The event
85953      * @param {Object} data An object containing arbitrary data supplied by the drag source
85954      * @return {String} status The CSS class that communicates the drop status back to the source so that the
85955      * underlying {@link Ext.dd.StatusProxy} can be updated
85956      */
85957     onNodeOver : function(n, dd, e, data){
85958         return this.dropAllowed;
85959     },
85960
85961     /**
85962      * Called when the DropZone determines that a {@link Ext.dd.DragSource} has been dragged out of
85963      * the drop node without dropping.  This method has no default implementation and should be overridden to provide
85964      * node-specific processing if necessary.
85965      * @param {Object} nodeData The custom data associated with the drop node (this is the same value returned from
85966      * {@link #getTargetFromEvent} for this node)
85967      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
85968      * @param {Event} e The event
85969      * @param {Object} data An object containing arbitrary data supplied by the drag source
85970      */
85971     onNodeOut : function(n, dd, e, data){
85972         
85973     },
85974
85975     /**
85976      * Called when the DropZone determines that a {@link Ext.dd.DragSource} has been dropped onto
85977      * the drop node.  The default implementation returns false, so it should be overridden to provide the
85978      * appropriate processing of the drop event and return true so that the drag source's repair action does not run.
85979      * @param {Object} nodeData The custom data associated with the drop node (this is the same value returned from
85980      * {@link #getTargetFromEvent} for this node)
85981      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
85982      * @param {Event} e The event
85983      * @param {Object} data An object containing arbitrary data supplied by the drag source
85984      * @return {Boolean} True if the drop was valid, else false
85985      */
85986     onNodeDrop : function(n, dd, e, data){
85987         return false;
85988     },
85989
85990     /**
85991      * Called while the DropZone determines that a {@link Ext.dd.DragSource} is being dragged over it,
85992      * but not over any of its registered drop nodes.  The default implementation returns this.dropNotAllowed, so
85993      * it should be overridden to provide the proper feedback if necessary.
85994      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
85995      * @param {Event} e The event
85996      * @param {Object} data An object containing arbitrary data supplied by the drag source
85997      * @return {String} status The CSS class that communicates the drop status back to the source so that the
85998      * underlying {@link Ext.dd.StatusProxy} can be updated
85999      */
86000     onContainerOver : function(dd, e, data){
86001         return this.dropNotAllowed;
86002     },
86003
86004     /**
86005      * Called when the DropZone determines that a {@link Ext.dd.DragSource} has been dropped on it,
86006      * but not on any of its registered drop nodes.  The default implementation returns false, so it should be
86007      * overridden to provide the appropriate processing of the drop event if you need the drop zone itself to
86008      * be able to accept drops.  It should return true when valid so that the drag source's repair action does not run.
86009      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
86010      * @param {Event} e The event
86011      * @param {Object} data An object containing arbitrary data supplied by the drag source
86012      * @return {Boolean} True if the drop was valid, else false
86013      */
86014     onContainerDrop : function(dd, e, data){
86015         return false;
86016     },
86017
86018     /**
86019      * The function a {@link Ext.dd.DragSource} calls once to notify this drop zone that the source is now over
86020      * the zone.  The default implementation returns this.dropNotAllowed and expects that only registered drop
86021      * nodes can process drag drop operations, so if you need the drop zone itself to be able to process drops
86022      * you should override this method and provide a custom implementation.
86023      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
86024      * @param {Event} e The event
86025      * @param {Object} data An object containing arbitrary data supplied by the drag source
86026      * @return {String} status The CSS class that communicates the drop status back to the source so that the
86027      * underlying {@link Ext.dd.StatusProxy} can be updated
86028      */
86029     notifyEnter : function(dd, e, data){
86030         return this.dropNotAllowed;
86031     },
86032
86033     /**
86034      * The function a {@link Ext.dd.DragSource} calls continuously while it is being dragged over the drop zone.
86035      * This method will be called on every mouse movement while the drag source is over the drop zone.
86036      * It will call {@link #onNodeOver} while the drag source is over a registered node, and will also automatically
86037      * delegate to the appropriate node-specific methods as necessary when the drag source enters and exits
86038      * registered nodes ({@link #onNodeEnter}, {@link #onNodeOut}). If the drag source is not currently over a
86039      * registered node, it will call {@link #onContainerOver}.
86040      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
86041      * @param {Event} e The event
86042      * @param {Object} data An object containing arbitrary data supplied by the drag source
86043      * @return {String} status The CSS class that communicates the drop status back to the source so that the
86044      * underlying {@link Ext.dd.StatusProxy} can be updated
86045      */
86046     notifyOver : function(dd, e, data){
86047         var n = this.getTargetFromEvent(e);
86048         if(!n) { // not over valid drop target
86049             if(this.lastOverNode){
86050                 this.onNodeOut(this.lastOverNode, dd, e, data);
86051                 this.lastOverNode = null;
86052             }
86053             return this.onContainerOver(dd, e, data);
86054         }
86055         if(this.lastOverNode != n){
86056             if(this.lastOverNode){
86057                 this.onNodeOut(this.lastOverNode, dd, e, data);
86058             }
86059             this.onNodeEnter(n, dd, e, data);
86060             this.lastOverNode = n;
86061         }
86062         return this.onNodeOver(n, dd, e, data);
86063     },
86064
86065     /**
86066      * The function a {@link Ext.dd.DragSource} calls once to notify this drop zone that the source has been dragged
86067      * out of the zone without dropping.  If the drag source is currently over a registered node, the notification
86068      * will be delegated to {@link #onNodeOut} for node-specific handling, otherwise it will be ignored.
86069      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop target
86070      * @param {Event} e The event
86071      * @param {Object} data An object containing arbitrary data supplied by the drag zone
86072      */
86073     notifyOut : function(dd, e, data){
86074         if(this.lastOverNode){
86075             this.onNodeOut(this.lastOverNode, dd, e, data);
86076             this.lastOverNode = null;
86077         }
86078     },
86079
86080     /**
86081      * The function a {@link Ext.dd.DragSource} calls once to notify this drop zone that the dragged item has
86082      * been dropped on it.  The drag zone will look up the target node based on the event passed in, and if there
86083      * is a node registered for that event, it will delegate to {@link #onNodeDrop} for node-specific handling,
86084      * otherwise it will call {@link #onContainerDrop}.
86085      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
86086      * @param {Event} e The event
86087      * @param {Object} data An object containing arbitrary data supplied by the drag source
86088      * @return {Boolean} False if the drop was invalid.
86089      */
86090     notifyDrop : function(dd, e, data){
86091         if(this.lastOverNode){
86092             this.onNodeOut(this.lastOverNode, dd, e, data);
86093             this.lastOverNode = null;
86094         }
86095         var n = this.getTargetFromEvent(e);
86096         return n ?
86097             this.onNodeDrop(n, dd, e, data) :
86098             this.onContainerDrop(dd, e, data);
86099     },
86100
86101     // private
86102     triggerCacheRefresh : function() {
86103         Ext.dd.DDM.refreshCache(this.groups);
86104     }
86105 });
86106 /**
86107  * @class Ext.flash.Component
86108  * @extends Ext.Component
86109  *
86110  * A simple Component for displaying an Adobe Flash SWF movie. The movie will be sized and can participate
86111  * in layout like any other Component.
86112  *
86113  * This component requires the third-party SWFObject library version 2.2 or above. It is not included within
86114  * the ExtJS distribution, so you will have to include it into your page manually in order to use this component.
86115  * The SWFObject library can be downloaded from the [SWFObject project page](http://code.google.com/p/swfobject)
86116  * and then simply import it into the head of your HTML document:
86117  *
86118  *     <script type="text/javascript" src="path/to/local/swfobject.js"></script>
86119  *
86120  * ## Configuration
86121  *
86122  * This component allows several options for configuring how the target Flash movie is embedded. The most
86123  * important is the required {@link #url} which points to the location of the Flash movie to load. Other
86124  * configurations include:
86125  *
86126  * - {@link #backgroundColor}
86127  * - {@link #wmode}
86128  * - {@link #flashVars}
86129  * - {@link #flashParams}
86130  * - {@link #flashAttributes}
86131  *
86132  * ## Example usage:
86133  *
86134  *     var win = Ext.widget('window', {
86135  *         title: "It's a tiger!",
86136  *         layout: 'fit',
86137  *         width: 300,
86138  *         height: 300,
86139  *         x: 20,
86140  *         y: 20,
86141  *         resizable: true,
86142  *         items: {
86143  *             xtype: 'flash',
86144  *             url: 'tiger.swf'
86145  *         }
86146  *     });
86147  *     win.show();
86148  *
86149  * ## Express Install
86150  *
86151  * Adobe provides a tool called [Express Install](http://www.adobe.com/devnet/flashplayer/articles/express_install.html)
86152  * that offers users an easy way to upgrade their Flash player. If you wish to make use of this, you should set
86153  * the static EXPRESS\_INSTALL\_URL property to the location of your Express Install SWF file:
86154  *
86155  *     Ext.flash.Component.EXPRESS_INSTALL_URL = 'path/to/local/expressInstall.swf';
86156  *
86157  * @constructor
86158  * Creates a new Ext.flash.Component instance.
86159  * @param {Object} config The component configuration.
86160  *
86161  * @xtype flash
86162  * @docauthor Jason Johnston <jason@sencha.com>
86163  */
86164 Ext.define('Ext.flash.Component', {
86165     extend: 'Ext.Component',
86166     alternateClassName: 'Ext.FlashComponent',
86167     alias: 'widget.flash',
86168
86169     /**
86170      * @cfg {String} flashVersion
86171      * Indicates the version the flash content was published for. Defaults to <tt>'9.0.115'</tt>.
86172      */
86173     flashVersion : '9.0.115',
86174
86175     /**
86176      * @cfg {String} backgroundColor
86177      * The background color of the SWF movie. Defaults to <tt>'#ffffff'</tt>.
86178      */
86179     backgroundColor: '#ffffff',
86180
86181     /**
86182      * @cfg {String} wmode
86183      * The wmode of the flash object. This can be used to control layering. Defaults to <tt>'opaque'</tt>.
86184      * Set to 'transparent' to ignore the {@link #backgroundColor} and make the background of the Flash
86185      * movie transparent.
86186      */
86187     wmode: 'opaque',
86188
86189     /**
86190      * @cfg {Object} flashVars
86191      * A set of key value pairs to be passed to the flash object as flash variables. Defaults to <tt>undefined</tt>.
86192      */
86193
86194     /**
86195      * @cfg {Object} flashParams
86196      * A set of key value pairs to be passed to the flash object as parameters. Possible parameters can be found here:
86197      * http://kb2.adobe.com/cps/127/tn_12701.html Defaults to <tt>undefined</tt>.
86198      */
86199
86200     /**
86201      * @cfg {Object} flashAttributes
86202      * A set of key value pairs to be passed to the flash object as attributes. Defaults to <tt>undefined</tt>.
86203      */
86204
86205     /**
86206      * @cfg {String} url
86207      * The URL of the SWF file to include. Required.
86208      */
86209
86210     /**
86211      * @cfg {String/Number} swfWidth The width of the embedded SWF movie inside the component. Defaults to "100%"
86212      * so that the movie matches the width of the component.
86213      */
86214     swfWidth: '100%',
86215
86216     /**
86217      * @cfg {String/Number} swfHeight The height of the embedded SWF movie inside the component. Defaults to "100%"
86218      * so that the movie matches the height of the component.
86219      */
86220     swfHeight: '100%',
86221
86222     /**
86223      * @cfg {Boolean} expressInstall
86224      * True to prompt the user to install flash if not installed. Note that this uses
86225      * Ext.FlashComponent.EXPRESS_INSTALL_URL, which should be set to the local resource. Defaults to <tt>false</tt>.
86226      */
86227     expressInstall: false,
86228
86229     /**
86230      * @property swf
86231      * @type {Ext.core.Element}
86232      * A reference to the object or embed element into which the SWF file is loaded. Only
86233      * populated after the component is rendered and the SWF has been successfully embedded.
86234      */
86235
86236     // Have to create a placeholder div with the swfId, which SWFObject will replace with the object/embed element.
86237     renderTpl: ['<div id="{swfId}"></div>'],
86238
86239     initComponent: function() {
86240         if (!('swfobject' in window)) {
86241             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/');
86242         }
86243         if (!this.url) {
86244             Ext.Error.raise('The "url" config is required for Ext.flash.Component');
86245         }
86246
86247         this.callParent();
86248         this.addEvents(
86249             /**
86250              * @event success
86251              * Fired when the Flash movie has been successfully embedded
86252              * @param {Ext.flash.Component} this
86253              */
86254             'success',
86255
86256             /**
86257              * @event failure
86258              * Fired when the Flash movie embedding fails
86259              * @param {Ext.flash.Component} this
86260              */
86261             'failure'
86262         );
86263     },
86264
86265     onRender: function() {
86266         var me = this,
86267             params, vars, undef,
86268             swfId = me.getSwfId();
86269
86270         me.renderData.swfId = swfId;
86271
86272         me.callParent(arguments);
86273
86274         params = Ext.apply({
86275             allowScriptAccess: 'always',
86276             bgcolor: me.backgroundColor,
86277             wmode: me.wmode
86278         }, me.flashParams);
86279
86280         vars = Ext.apply({
86281             allowedDomain: document.location.hostname
86282         }, me.flashVars);
86283
86284         new swfobject.embedSWF(
86285             me.url,
86286             swfId,
86287             me.swfWidth,
86288             me.swfHeight,
86289             me.flashVersion,
86290             me.expressInstall ? me.statics.EXPRESS_INSTALL_URL : undef,
86291             vars,
86292             params,
86293             me.flashAttributes,
86294             Ext.bind(me.swfCallback, me)
86295         );
86296     },
86297
86298     /**
86299      * @private
86300      * The callback method for handling an embedding success or failure by SWFObject
86301      * @param {Object} e The event object passed by SWFObject - see http://code.google.com/p/swfobject/wiki/api
86302      */
86303     swfCallback: function(e) {
86304         var me = this;
86305         if (e.success) {
86306             me.swf = Ext.get(e.ref);
86307             me.onSuccess();
86308             me.fireEvent('success', me);
86309         } else {
86310             me.onFailure();
86311             me.fireEvent('failure', me);
86312         }
86313     },
86314
86315     /**
86316      * Retrieve the id of the SWF object/embed element
86317      */
86318     getSwfId: function() {
86319         return this.swfId || (this.swfId = "extswf" + this.getAutoId());
86320     },
86321
86322     onSuccess: function() {
86323         // swfobject forces visiblity:visible on the swf element, which prevents it 
86324         // from getting hidden when an ancestor is given visibility:hidden.
86325         this.swf.setStyle('visibility', 'inherit');
86326     },
86327
86328     onFailure: Ext.emptyFn,
86329
86330     beforeDestroy: function() {
86331         var me = this,
86332             swf = me.swf;
86333         if (swf) {
86334             swfobject.removeSWF(me.getSwfId());
86335             Ext.destroy(swf);
86336             delete me.swf;
86337         }
86338         me.callParent();
86339     },
86340
86341     statics: {
86342         /**
86343          * Sets the url for installing flash if it doesn't exist. This should be set to a local resource.
86344          * See http://www.adobe.com/devnet/flashplayer/articles/express_install.html for details.
86345          * @static
86346          * @type String
86347          */
86348         EXPRESS_INSTALL_URL: 'http:/' + '/swfobject.googlecode.com/svn/trunk/swfobject/expressInstall.swf'
86349     }
86350 });
86351
86352 /**
86353  * @class Ext.form.action.Action
86354  * @extends Ext.Base
86355  * <p>The subclasses of this class provide actions to perform upon {@link Ext.form.Basic Form}s.</p>
86356  * <p>Instances of this class are only created by a {@link Ext.form.Basic Form} when
86357  * the Form needs to perform an action such as submit or load. The Configuration options
86358  * listed for this class are set through the Form's action methods: {@link Ext.form.Basic#submit submit},
86359  * {@link Ext.form.Basic#load load} and {@link Ext.form.Basic#doAction doAction}</p>
86360  * <p>The instance of Action which performed the action is passed to the success
86361  * and failure callbacks of the Form's action methods ({@link Ext.form.Basic#submit submit},
86362  * {@link Ext.form.Basic#load load} and {@link Ext.form.Basic#doAction doAction}),
86363  * and to the {@link Ext.form.Basic#actioncomplete actioncomplete} and
86364  * {@link Ext.form.Basic#actionfailed actionfailed} event handlers.</p>
86365  * @constructor
86366  * @param {Object} config The configuration for this instance.
86367  */
86368 Ext.define('Ext.form.action.Action', {
86369     alternateClassName: 'Ext.form.Action',
86370
86371     /**
86372      * @cfg {Ext.form.Basic} form The {@link Ext.form.Basic BasicForm} instance that
86373      * is invoking this Action. Required.
86374      */
86375
86376     /**
86377      * @cfg {String} url The URL that the Action is to invoke. Will default to the {@link Ext.form.Basic#url url}
86378      * configured on the {@link #form}.
86379      */
86380
86381     /**
86382      * @cfg {Boolean} reset When set to <tt><b>true</b></tt>, causes the Form to be
86383      * {@link Ext.form.Basic#reset reset} on Action success. If specified, this happens
86384      * before the {@link #success} callback is called and before the Form's
86385      * {@link Ext.form.Basic#actioncomplete actioncomplete} event fires.
86386      */
86387
86388     /**
86389      * @cfg {String} method The HTTP method to use to access the requested URL. Defaults to the
86390      * {@link Ext.form.Basic#method BasicForm's method}, or 'POST' if not specified.
86391      */
86392
86393     /**
86394      * @cfg {Object/String} params <p>Extra parameter values to pass. These are added to the Form's
86395      * {@link Ext.form.Basic#baseParams} and passed to the specified URL along with the Form's
86396      * input fields.</p>
86397      * <p>Parameters are encoded as standard HTTP parameters using {@link Ext#urlEncode Ext.Object.toQueryString}.</p>
86398      */
86399
86400     /**
86401      * @cfg {Object} headers <p>Extra headers to be sent in the AJAX request for submit and load actions. See
86402      * {@link Ext.data.Connection#headers}.</p>
86403      */
86404
86405     /**
86406      * @cfg {Number} timeout The number of seconds to wait for a server response before
86407      * failing with the {@link #failureType} as {@link Ext.form.action.Action#CONNECT_FAILURE}. If not specified,
86408      * defaults to the configured <tt>{@link Ext.form.Basic#timeout timeout}</tt> of the
86409      * {@link #form}.
86410      */
86411
86412     /**
86413      * @cfg {Function} success The function to call when a valid success return packet is received.
86414      * The function is passed the following parameters:<ul class="mdetail-params">
86415      * <li><b>form</b> : Ext.form.Basic<div class="sub-desc">The form that requested the action</div></li>
86416      * <li><b>action</b> : Ext.form.action.Action<div class="sub-desc">The Action class. The {@link #result}
86417      * property of this object may be examined to perform custom postprocessing.</div></li>
86418      * </ul>
86419      */
86420
86421     /**
86422      * @cfg {Function} failure The function to call when a failure packet was received, or when an
86423      * error ocurred in the Ajax communication.
86424      * The function is passed the following parameters:<ul class="mdetail-params">
86425      * <li><b>form</b> : Ext.form.Basic<div class="sub-desc">The form that requested the action</div></li>
86426      * <li><b>action</b> : Ext.form.action.Action<div class="sub-desc">The Action class. If an Ajax
86427      * error ocurred, the failure type will be in {@link #failureType}. The {@link #result}
86428      * property of this object may be examined to perform custom postprocessing.</div></li>
86429      * </ul>
86430      */
86431
86432     /**
86433      * @cfg {Object} scope The scope in which to call the configured <tt>success</tt> and <tt>failure</tt>
86434      * callback functions (the <tt>this</tt> reference for the callback functions).
86435      */
86436
86437     /**
86438      * @cfg {String} waitMsg The message to be displayed by a call to {@link Ext.window.MessageBox#wait}
86439      * during the time the action is being processed.
86440      */
86441
86442     /**
86443      * @cfg {String} waitTitle The title to be displayed by a call to {@link Ext.window.MessageBox#wait}
86444      * during the time the action is being processed.
86445      */
86446
86447     /**
86448      * @cfg {Boolean} submitEmptyText If set to <tt>true</tt>, the emptyText value will be sent with the form
86449      * when it is submitted. Defaults to <tt>true</tt>.
86450      */
86451
86452     /**
86453      * @property type
86454      * The type of action this Action instance performs.
86455      * Currently only "submit" and "load" are supported.
86456      * @type {String}
86457      */
86458
86459     /**
86460      * The type of failure detected will be one of these: {@link Ext.form.action.Action#CLIENT_INVALID},
86461      * {@link Ext.form.action.Action#SERVER_INVALID}, {@link Ext.form.action.Action#CONNECT_FAILURE}, or
86462      * {@link Ext.form.action.Action#LOAD_FAILURE}.  Usage:
86463      * <pre><code>
86464 var fp = new Ext.form.Panel({
86465 ...
86466 buttons: [{
86467     text: 'Save',
86468     formBind: true,
86469     handler: function(){
86470         if(fp.getForm().isValid()){
86471             fp.getForm().submit({
86472                 url: 'form-submit.php',
86473                 waitMsg: 'Submitting your data...',
86474                 success: function(form, action){
86475                     // server responded with success = true
86476                     var result = action.{@link #result};
86477                 },
86478                 failure: function(form, action){
86479                     if (action.{@link #failureType} === {@link Ext.form.action.Action#CONNECT_FAILURE}) {
86480                         Ext.Msg.alert('Error',
86481                             'Status:'+action.{@link #response}.status+': '+
86482                             action.{@link #response}.statusText);
86483                     }
86484                     if (action.failureType === {@link Ext.form.action.Action#SERVER_INVALID}){
86485                         // server responded with success = false
86486                         Ext.Msg.alert('Invalid', action.{@link #result}.errormsg);
86487                     }
86488                 }
86489             });
86490         }
86491     }
86492 },{
86493     text: 'Reset',
86494     handler: function(){
86495         fp.getForm().reset();
86496     }
86497 }]
86498      * </code></pre>
86499      * @property failureType
86500      * @type {String}
86501      */
86502
86503     /**
86504      * The raw XMLHttpRequest object used to perform the action.
86505      * @property response
86506      * @type {Object}
86507      */
86508
86509     /**
86510      * The decoded response object containing a boolean <tt>success</tt> property and
86511      * other, action-specific properties.
86512      * @property result
86513      * @type {Object}
86514      */
86515
86516
86517
86518     constructor: function(config) {
86519         if (config) {
86520             Ext.apply(this, config);
86521         }
86522
86523         // Normalize the params option to an Object
86524         var params = config.params;
86525         if (Ext.isString(params)) {
86526             this.params = Ext.Object.fromQueryString(params);
86527         }
86528     },
86529
86530     /**
86531      * Invokes this action using the current configuration.
86532      */
86533     run: Ext.emptyFn,
86534
86535     /**
86536      * @private
86537      * @method onSuccess
86538      * Callback method that gets invoked when the action completes successfully. Must be implemented by subclasses.
86539      * @param {Object} response
86540      */
86541
86542     /**
86543      * @private
86544      * @method handleResponse
86545      * Handles the raw response and builds a result object from it. Must be implemented by subclasses.
86546      * @param {Object} response
86547      */
86548
86549     /**
86550      * @private
86551      * Handles a failure response.
86552      * @param {Object} response
86553      */
86554     onFailure : function(response){
86555         this.response = response;
86556         this.failureType = Ext.form.action.Action.CONNECT_FAILURE;
86557         this.form.afterAction(this, false);
86558     },
86559
86560     /**
86561      * @private
86562      * Validates that a response contains either responseText or responseXML and invokes
86563      * {@link #handleResponse} to build the result object.
86564      * @param {Object} response The raw response object.
86565      * @return {Object/Boolean} result The result object as built by handleResponse, or <tt>true</tt> if
86566      *                         the response had empty responseText and responseXML.
86567      */
86568     processResponse : function(response){
86569         this.response = response;
86570         if (!response.responseText && !response.responseXML) {
86571             return true;
86572         }
86573         return (this.result = this.handleResponse(response));
86574     },
86575
86576     /**
86577      * @private
86578      * Build the URL for the AJAX request. Used by the standard AJAX submit and load actions.
86579      * @return {String} The URL.
86580      */
86581     getUrl: function() {
86582         return this.url || this.form.url;
86583     },
86584
86585     /**
86586      * @private
86587      * Determine the HTTP method to be used for the request.
86588      * @return {String} The HTTP method
86589      */
86590     getMethod: function() {
86591         return (this.method || this.form.method || 'POST').toUpperCase();
86592     },
86593
86594     /**
86595      * @private
86596      * Get the set of parameters specified in the BasicForm's baseParams and/or the params option.
86597      * Items in params override items of the same name in baseParams.
86598      * @return {Object} the full set of parameters
86599      */
86600     getParams: function() {
86601         return Ext.apply({}, this.params, this.form.baseParams);
86602     },
86603
86604     /**
86605      * @private
86606      * Creates a callback object.
86607      */
86608     createCallback: function() {
86609         var me = this,
86610             undef,
86611             form = me.form;
86612         return {
86613             success: me.onSuccess,
86614             failure: me.onFailure,
86615             scope: me,
86616             timeout: (this.timeout * 1000) || (form.timeout * 1000),
86617             upload: form.fileUpload ? me.onSuccess : undef
86618         };
86619     },
86620
86621     statics: {
86622         /**
86623          * @property CLIENT_INVALID
86624          * Failure type returned when client side validation of the Form fails
86625          * thus aborting a submit action. Client side validation is performed unless
86626          * {@link Ext.form.action.Submit#clientValidation} is explicitly set to <tt>false</tt>.
86627          * @type {String}
86628          * @static
86629          */
86630         CLIENT_INVALID: 'client',
86631
86632         /**
86633          * @property SERVER_INVALID
86634          * <p>Failure type returned when server side processing fails and the {@link #result}'s
86635          * <tt>success</tt> property is set to <tt>false</tt>.</p>
86636          * <p>In the case of a form submission, field-specific error messages may be returned in the
86637          * {@link #result}'s <tt>errors</tt> property.</p>
86638          * @type {String}
86639          * @static
86640          */
86641         SERVER_INVALID: 'server',
86642
86643         /**
86644          * @property CONNECT_FAILURE
86645          * Failure type returned when a communication error happens when attempting
86646          * to send a request to the remote server. The {@link #response} may be examined to
86647          * provide further information.
86648          * @type {String}
86649          * @static
86650          */
86651         CONNECT_FAILURE: 'connect',
86652
86653         /**
86654          * @property LOAD_FAILURE
86655          * Failure type returned when the response's <tt>success</tt>
86656          * property is set to <tt>false</tt>, or no field values are returned in the response's
86657          * <tt>data</tt> property.
86658          * @type {String}
86659          * @static
86660          */
86661         LOAD_FAILURE: 'load'
86662
86663
86664     }
86665 });
86666
86667 /**
86668  * @class Ext.form.action.Submit
86669  * @extends Ext.form.action.Action
86670  * <p>A class which handles submission of data from {@link Ext.form.Basic Form}s
86671  * and processes the returned response.</p>
86672  * <p>Instances of this class are only created by a {@link Ext.form.Basic Form} when
86673  * {@link Ext.form.Basic#submit submit}ting.</p>
86674  * <p><u><b>Response Packet Criteria</b></u></p>
86675  * <p>A response packet may contain:
86676  * <div class="mdetail-params"><ul>
86677  * <li><b><code>success</code></b> property : Boolean
86678  * <div class="sub-desc">The <code>success</code> property is required.</div></li>
86679  * <li><b><code>errors</code></b> property : Object
86680  * <div class="sub-desc"><div class="sub-desc">The <code>errors</code> property,
86681  * which is optional, contains error messages for invalid fields.</div></li>
86682  * </ul></div>
86683  * <p><u><b>JSON Packets</b></u></p>
86684  * <p>By default, response packets are assumed to be JSON, so a typical response
86685  * packet may look like this:</p><pre><code>
86686 {
86687     success: false,
86688     errors: {
86689         clientCode: "Client not found",
86690         portOfLoading: "This field must not be null"
86691     }
86692 }</code></pre>
86693  * <p>Other data may be placed into the response for processing by the {@link Ext.form.Basic}'s callback
86694  * or event handler methods. The object decoded from this JSON is available in the
86695  * {@link Ext.form.action.Action#result result} property.</p>
86696  * <p>Alternatively, if an {@link #errorReader} is specified as an {@link Ext.data.reader.Xml XmlReader}:</p><pre><code>
86697     errorReader: new Ext.data.reader.Xml({
86698             record : 'field',
86699             success: '@success'
86700         }, [
86701             'id', 'msg'
86702         ]
86703     )
86704 </code></pre>
86705  * <p>then the results may be sent back in XML format:</p><pre><code>
86706 &lt;?xml version="1.0" encoding="UTF-8"?&gt;
86707 &lt;message success="false"&gt;
86708 &lt;errors&gt;
86709     &lt;field&gt;
86710         &lt;id&gt;clientCode&lt;/id&gt;
86711         &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;
86712     &lt;/field&gt;
86713     &lt;field&gt;
86714         &lt;id&gt;portOfLoading&lt;/id&gt;
86715         &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;
86716     &lt;/field&gt;
86717 &lt;/errors&gt;
86718 &lt;/message&gt;
86719 </code></pre>
86720  * <p>Other elements may be placed into the response XML for processing by the {@link Ext.form.Basic}'s callback
86721  * or event handler methods. The XML document is available in the {@link #errorReader}'s {@link Ext.data.reader.Xml#xmlData xmlData} property.</p>
86722  */
86723 Ext.define('Ext.form.action.Submit', {
86724     extend:'Ext.form.action.Action',
86725     alternateClassName: 'Ext.form.Action.Submit',
86726     alias: 'formaction.submit',
86727
86728     type: 'submit',
86729
86730     /**
86731      * @cfg {boolean} clientValidation Determines whether a Form's fields are validated
86732      * in a final call to {@link Ext.form.Basic#isValid isValid} prior to submission.
86733      * Pass <tt>false</tt> in the Form's submit options to prevent this. Defaults to true.
86734      */
86735
86736     // inherit docs
86737     run : function(){
86738         var form = this.form;
86739         if (this.clientValidation === false || form.isValid()) {
86740             this.doSubmit();
86741         } else {
86742             // client validation failed
86743             this.failureType = Ext.form.action.Action.CLIENT_INVALID;
86744             form.afterAction(this, false);
86745         }
86746     },
86747
86748     /**
86749      * @private
86750      * Perform the submit of the form data.
86751      */
86752     doSubmit: function() {
86753         var formEl,
86754             ajaxOptions = Ext.apply(this.createCallback(), {
86755                 url: this.getUrl(),
86756                 method: this.getMethod(),
86757                 headers: this.headers
86758             });
86759
86760         // For uploads we need to create an actual form that contains the file upload fields,
86761         // and pass that to the ajax call so it can do its iframe-based submit method.
86762         if (this.form.hasUpload()) {
86763             formEl = ajaxOptions.form = this.buildForm();
86764             ajaxOptions.isUpload = true;
86765         } else {
86766             ajaxOptions.params = this.getParams();
86767         }
86768
86769         Ext.Ajax.request(ajaxOptions);
86770
86771         if (formEl) {
86772             Ext.removeNode(formEl);
86773         }
86774     },
86775
86776     /**
86777      * @private
86778      * Build the full set of parameters from the field values plus any additional configured params.
86779      */
86780     getParams: function() {
86781         var nope = false,
86782             configParams = this.callParent(),
86783             fieldParams = this.form.getValues(nope, nope, this.submitEmptyText !== nope);
86784         return Ext.apply({}, fieldParams, configParams);
86785     },
86786
86787     /**
86788      * @private
86789      * Build a form element containing fields corresponding to all the parameters to be
86790      * submitted (everything returned by {@link #getParams}.
86791      * NOTE: the form element is automatically added to the DOM, so any code that uses
86792      * it must remove it from the DOM after finishing with it.
86793      * @return HTMLFormElement
86794      */
86795     buildForm: function() {
86796         var fieldsSpec = [],
86797             formSpec,
86798             formEl,
86799             basicForm = this.form,
86800             params = this.getParams(),
86801             uploadFields = [];
86802
86803         basicForm.getFields().each(function(field) {
86804             if (field.isFileUpload()) {
86805                 uploadFields.push(field);
86806             }
86807         });
86808
86809         function addField(name, val) {
86810             fieldsSpec.push({
86811                 tag: 'input',
86812                 type: 'hidden',
86813                 name: name,
86814                 value: val
86815             });
86816         }
86817
86818         // Add the form field values
86819         Ext.iterate(params, function(key, val) {
86820             if (Ext.isArray(val)) {
86821                 Ext.each(val, function(v) {
86822                     addField(key, v);
86823                 });
86824             } else {
86825                 addField(key, val);
86826             }
86827         });
86828
86829         formSpec = {
86830             tag: 'form',
86831             action: this.getUrl(),
86832             method: this.getMethod(),
86833             target: this.target || '_self',
86834             style: 'display:none',
86835             cn: fieldsSpec
86836         };
86837
86838         // Set the proper encoding for file uploads
86839         if (uploadFields.length) {
86840             formSpec.encoding = formSpec.enctype = 'multipart/form-data';
86841         }
86842
86843         // Create the form
86844         formEl = Ext.core.DomHelper.append(Ext.getBody(), formSpec);
86845
86846         // Special handling for file upload fields: since browser security measures prevent setting
86847         // their values programatically, and prevent carrying their selected values over when cloning,
86848         // we have to move the actual field instances out of their components and into the form.
86849         Ext.Array.each(uploadFields, function(field) {
86850             if (field.rendered) { // can only have a selected file value after being rendered
86851                 formEl.appendChild(field.extractFileInput());
86852             }
86853         });
86854
86855         return formEl;
86856     },
86857
86858
86859
86860     /**
86861      * @private
86862      */
86863     onSuccess: function(response) {
86864         var form = this.form,
86865             success = true,
86866             result = this.processResponse(response);
86867         if (result !== true && !result.success) {
86868             if (result.errors) {
86869                 form.markInvalid(result.errors);
86870             }
86871             this.failureType = Ext.form.action.Action.SERVER_INVALID;
86872             success = false;
86873         }
86874         form.afterAction(this, success);
86875     },
86876
86877     /**
86878      * @private
86879      */
86880     handleResponse: function(response) {
86881         var form = this.form,
86882             errorReader = form.errorReader,
86883             rs, errors, i, len, records;
86884         if (errorReader) {
86885             rs = errorReader.read(response);
86886             records = rs.records;
86887             errors = [];
86888             if (records) {
86889                 for(i = 0, len = records.length; i < len; i++) {
86890                     errors[i] = records[i].data;
86891                 }
86892             }
86893             if (errors.length < 1) {
86894                 errors = null;
86895             }
86896             return {
86897                 success : rs.success,
86898                 errors : errors
86899             };
86900         }
86901         return Ext.decode(response.responseText);
86902     }
86903 });
86904
86905 /**
86906  * @class Ext.util.ComponentDragger
86907  * @extends Ext.dd.DragTracker
86908  * <p>A subclass of Ext.dd.DragTracker which handles dragging any Component.</p>
86909  * <p>This is configured with a Component to be made draggable, and a config object for the
86910  * {@link Ext.dd.DragTracker} class.</p>
86911  * <p>A {@link #} delegate may be provided which may be either the element to use as the mousedown target
86912  * or a {@link Ext.DomQuery} selector to activate multiple mousedown targets.</p>
86913  * @constructor Create a new ComponentTracker
86914  * @param {object} comp The Component to provide dragging for.
86915  * @param {object} config The config object
86916  */
86917 Ext.define('Ext.util.ComponentDragger', {
86918
86919     /**
86920      * @cfg {Boolean} constrain
86921      * Specify as <code>true</code> to constrain the Component to within the bounds of the {@link #constrainTo} region.
86922      */
86923
86924     /**
86925      * @cfg {String/Element} delegate
86926      * Optional. <p>A {@link Ext.DomQuery DomQuery} selector which identifies child elements within the Component's encapsulating
86927      * Element which are the drag handles. This limits dragging to only begin when the matching elements are mousedowned.</p>
86928      * <p>This may also be a specific child element within the Component's encapsulating element to use as the drag handle.</p>
86929      */
86930
86931     /**
86932      * @cfg {Boolean} constrainDelegate
86933      * Specify as <code>true</code> to constrain the drag handles within the {@link constrainTo} region.
86934      */
86935
86936     extend: 'Ext.dd.DragTracker',
86937
86938     autoStart: 500,
86939
86940     constructor: function(comp, config) {
86941         this.comp = comp;
86942         this.initialConstrainTo = config.constrainTo;
86943         this.callParent([ config ]);
86944     },
86945
86946     onStart: function(e) {
86947         var me = this,
86948             comp = me.comp;
86949
86950         // Cache the start [X, Y] array
86951         this.startPosition = comp.getPosition();
86952
86953         // If client Component has a ghost method to show a lightweight version of itself
86954         // then use that as a drag proxy unless configured to liveDrag.
86955         if (comp.ghost && !comp.liveDrag) {
86956              me.proxy = comp.ghost();
86957              me.dragTarget = me.proxy.header.el;
86958         }
86959
86960         // Set the constrainTo Region before we start dragging.
86961         if (me.constrain || me.constrainDelegate) {
86962             me.constrainTo = me.calculateConstrainRegion();
86963         }
86964     },
86965
86966     calculateConstrainRegion: function() {
86967         var me = this,
86968             comp = me.comp,
86969             c = me.initialConstrainTo,
86970             delegateRegion,
86971             elRegion,
86972             shadowSize = comp.el.shadow ? comp.el.shadow.offset : 0;
86973
86974         // The configured constrainTo might be a Region or an element
86975         if (!(c instanceof Ext.util.Region)) {
86976             c =  Ext.fly(c).getViewRegion();
86977         }
86978
86979         // Reduce the constrain region to allow for shadow
86980         if (shadowSize) {
86981             c.adjust(0, -shadowSize, -shadowSize, shadowSize);
86982         }
86983
86984         // If they only want to constrain the *delegate* to within the constrain region,
86985         // adjust the region to be larger based on the insets of the delegate from the outer
86986         // edges of the Component.
86987         if (!me.constrainDelegate) {
86988             delegateRegion = Ext.fly(me.dragTarget).getRegion();
86989             elRegion = me.proxy ? me.proxy.el.getRegion() : comp.el.getRegion();
86990
86991             c.adjust(
86992                 delegateRegion.top - elRegion.top,
86993                 delegateRegion.right - elRegion.right,
86994                 delegateRegion.bottom - elRegion.bottom,
86995                 delegateRegion.left - elRegion.left
86996             );
86997         }
86998         return c;
86999     },
87000
87001     // Move either the ghost Component or the target Component to its new position on drag
87002     onDrag: function(e) {
87003         var me = this,
87004             comp = (me.proxy && !me.comp.liveDrag) ? me.proxy : me.comp,
87005             offset = me.getOffset(me.constrain || me.constrainDelegate ? 'dragTarget' : null);
87006
87007         comp.setPosition.apply(comp, [me.startPosition[0] + offset[0], me.startPosition[1] + offset[1]]);
87008     },
87009
87010     onEnd: function(e) {
87011         if (this.proxy && !this.comp.liveDrag) {
87012             this.comp.unghost();
87013         }
87014     }
87015 });
87016 /**
87017  * @class Ext.form.Labelable
87018
87019 A mixin which allows a component to be configured and decorated with a label and/or error message as is
87020 common for form fields. This is used by e.g. {@link Ext.form.field.Base} and {@link Ext.form.FieldContainer}
87021 to let them be managed by the Field layout.
87022
87023 **NOTE**: This mixin is mainly for internal library use and most users should not need to use it directly. It
87024 is more likely you will want to use one of the component classes that import this mixin, such as
87025 {@link Ext.form.field.Base} or {@link Ext.form.FieldContainer}.
87026
87027 Use of this mixin does not make a component a field in the logical sense, meaning it does not provide any
87028 logic or state related to values or validation; that is handled by the related {@link Ext.form.field.Field}
87029 mixin. These two mixins may be used separately (for example {@link Ext.form.FieldContainer} is Labelable but not a
87030 Field), or in combination (for example {@link Ext.form.field.Base} implements both and has logic for connecting the
87031 two.)
87032
87033 Component classes which use this mixin should use the Field layout
87034 or a derivation thereof to properly size and position the label and message according to the component config.
87035 They must also call the {@link #initLabelable} method during component initialization to ensure the mixin gets
87036 set up correctly.
87037
87038  * @markdown
87039  * @docauthor Jason Johnston <jason@sencha.com>
87040  */
87041 Ext.define("Ext.form.Labelable", {
87042     requires: ['Ext.XTemplate'],
87043
87044     /**
87045      * @cfg {Array/String/Ext.XTemplate} labelableRenderTpl
87046      * The rendering template for the field decorations. Component classes using this mixin should include
87047      * logic to use this as their {@link Ext.AbstractComponent#renderTpl renderTpl}, and implement the
87048      * {@link #getSubTplMarkup} method to generate the field body content.
87049      */
87050     labelableRenderTpl: [
87051         '<tpl if="!hideLabel && !(!fieldLabel && hideEmptyLabel)">',
87052             '<label<tpl if="inputId"> for="{inputId}"</tpl> class="{labelCls}"<tpl if="labelStyle"> style="{labelStyle}"</tpl>>',
87053                 '<tpl if="fieldLabel">{fieldLabel}{labelSeparator}</tpl>',
87054             '</label>',
87055         '</tpl>',
87056         '<div class="{baseBodyCls} {fieldBodyCls}"<tpl if="inputId"> id="{baseBodyCls}-{inputId}"</tpl> role="presentation">{subTplMarkup}</div>',
87057         '<div class="{errorMsgCls}" style="display:none"></div>',
87058         '<div class="{clearCls}" role="presentation"><!-- --></div>',
87059         {
87060             compiled: true,
87061             disableFormats: true
87062         }
87063     ],
87064
87065     /**
87066      * @cfg {Ext.XTemplate} activeErrorsTpl
87067      * The template used to format the Array of error messages passed to {@link #setActiveErrors}
87068      * into a single HTML string. By default this renders each message as an item in an unordered list.
87069      */
87070     activeErrorsTpl: [
87071         '<tpl if="errors && errors.length">',
87072             '<ul><tpl for="errors"><li<tpl if="xindex == xcount"> class="last"</tpl>>{.}</li></tpl></ul>',
87073         '</tpl>'
87074     ],
87075
87076     /**
87077      * @property isFieldLabelable
87078      * @type Boolean
87079      * Flag denoting that this object is labelable as a field. Always true.
87080      */
87081     isFieldLabelable: true,
87082
87083     /**
87084      * @cfg {String} formItemCls
87085      * A CSS class to be applied to the outermost element to denote that it is participating in the form
87086      * field layout. Defaults to 'x-form-item'.
87087      */
87088     formItemCls: Ext.baseCSSPrefix + 'form-item',
87089
87090     /**
87091      * @cfg {String} labelCls
87092      * The CSS class to be applied to the label element. Defaults to 'x-form-item-label'.
87093      */
87094     labelCls: Ext.baseCSSPrefix + 'form-item-label',
87095
87096     /**
87097      * @cfg {String} errorMsgCls
87098      * The CSS class to be applied to the error message element. Defaults to 'x-form-error-msg'.
87099      */
87100     errorMsgCls: Ext.baseCSSPrefix + 'form-error-msg',
87101
87102     /**
87103      * @cfg {String} baseBodyCls
87104      * The CSS class to be applied to the body content element. Defaults to 'x-form-item-body'.
87105      */
87106     baseBodyCls: Ext.baseCSSPrefix + 'form-item-body',
87107
87108     /**
87109      * @cfg {String} fieldBodyCls
87110      * An extra CSS class to be applied to the body content element in addition to {@link #fieldBodyCls}.
87111      * Defaults to empty.
87112      */
87113     fieldBodyCls: '',
87114
87115     /**
87116      * @cfg {String} clearCls
87117      * The CSS class to be applied to the special clearing div rendered directly after the field
87118      * contents wrapper to provide field clearing (defaults to <tt>'x-clear'</tt>).
87119      */
87120     clearCls: Ext.baseCSSPrefix + 'clear',
87121
87122     /**
87123      * @cfg {String} invalidCls
87124      * The CSS class to use when marking the component invalid (defaults to 'x-form-invalid')
87125      */
87126     invalidCls : Ext.baseCSSPrefix + 'form-invalid',
87127
87128     /**
87129      * @cfg {String} fieldLabel
87130      * The label for the field. It gets appended with the {@link #labelSeparator}, and its position
87131      * and sizing is determined by the {@link #labelAlign}, {@link #labelWidth}, and {@link #labelPad}
87132      * configs. Defaults to undefined.
87133      */
87134     fieldLabel: undefined,
87135
87136     /**
87137      * @cfg {String} labelAlign
87138      * <p>Controls the position and alignment of the {@link #fieldLabel}. Valid values are:</p>
87139      * <ul>
87140      * <li><tt>"left"</tt> (the default) - The label is positioned to the left of the field, with its text
87141      * aligned to the left. Its width is determined by the {@link #labelWidth} config.</li>
87142      * <li><tt>"top"</tt> - The label is positioned above the field.</li>
87143      * <li><tt>"right"</tt> - The label is positioned to the left of the field, with its text aligned
87144      * to the right. Its width is determined by the {@link #labelWidth} config.</li>
87145      * </ul>
87146      */
87147     labelAlign : 'left',
87148
87149     /**
87150      * @cfg {Number} labelWidth
87151      * The width of the {@link #fieldLabel} in pixels. Only applicable if the {@link #labelAlign} is set
87152      * to "left" or "right". Defaults to <tt>100</tt>.
87153      */
87154     labelWidth: 100,
87155
87156     /**
87157      * @cfg {Number} labelPad
87158      * The amount of space in pixels between the {@link #fieldLabel} and the input field. Defaults to <tt>5</tt>.
87159      */
87160     labelPad : 5,
87161
87162     /**
87163      * @cfg {String} labelSeparator
87164      * Character(s) to be inserted at the end of the {@link #fieldLabel label text}.
87165      */
87166     labelSeparator : ':',
87167
87168     /**
87169      * @cfg {String} labelStyle
87170      * <p>A CSS style specification string to apply directly to this field's label. Defaults to undefined.</p>
87171      */
87172
87173     /**
87174      * @cfg {Boolean} hideLabel
87175      * <p>Set to <tt>true</tt> to completely hide the label element ({@link #fieldLabel} and {@link #labelSeparator}).
87176      * Defaults to <tt>false</tt>.</p>
87177      * <p>Also see {@link #hideEmptyLabel}, which controls whether space will be reserved for an empty fieldLabel.</p>
87178      */
87179     hideLabel: false,
87180
87181     /**
87182      * @cfg {Boolean} hideEmptyLabel
87183      * <p>When set to <tt>true</tt>, the label element ({@link #fieldLabel} and {@link #labelSeparator}) will be
87184      * automatically hidden if the {@link #fieldLabel} is empty. Setting this to <tt>false</tt> will cause the empty
87185      * label element to be rendered and space to be reserved for it; this is useful if you want a field without a label
87186      * to line up with other labeled fields in the same form. Defaults to <tt>true</tt>.</p>
87187      * <p>If you wish to unconditionall hide the label even if a non-empty fieldLabel is configured, then set
87188      * the {@link #hideLabel} config to <tt>true</tt>.</p>
87189      */
87190     hideEmptyLabel: true,
87191
87192     /**
87193      * @cfg {Boolean} preventMark
87194      * <tt>true</tt> to disable displaying any {@link #setActiveError error message} set on this object.
87195      * Defaults to <tt>false</tt>.
87196      */
87197     preventMark: false,
87198
87199     /**
87200      * @cfg {Boolean} autoFitErrors
87201      * Whether to adjust the component's body area to make room for 'side' or 'under'
87202      * {@link #msgTarget error messages}. Defaults to <tt>true</tt>.
87203      */
87204     autoFitErrors: true,
87205
87206     /**
87207      * @cfg {String} msgTarget <p>The location where the error message text should display.
87208      * Must be one of the following values:</p>
87209      * <div class="mdetail-params"><ul>
87210      * <li><code>qtip</code> Display a quick tip containing the message when the user hovers over the field. This is the default.
87211      * <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>
87212      * <li><code>title</code> Display the message in a default browser title attribute popup.</li>
87213      * <li><code>under</code> Add a block div beneath the field containing the error message.</li>
87214      * <li><code>side</code> Add an error icon to the right of the field, displaying the message in a popup on hover.</li>
87215      * <li><code>none</code> Don't display any error message. This might be useful if you are implementing custom error display.</li>
87216      * <li><code>[element id]</code> Add the error message directly to the innerHTML of the specified element.</li>
87217      * </ul></div>
87218      */
87219     msgTarget: 'qtip',
87220
87221     /**
87222      * @cfg {String} activeError
87223      * If specified, then the component will be displayed with this value as its active error when
87224      * first rendered. Defaults to undefined. Use {@link #setActiveError} or {@link #unsetActiveError} to
87225      * change it after component creation.
87226      */
87227
87228
87229     /**
87230      * Performs initialization of this mixin. Component classes using this mixin should call this method
87231      * during their own initialization.
87232      */
87233     initLabelable: function() {
87234         this.addCls(this.formItemCls);
87235
87236         this.addEvents(
87237             /**
87238              * @event errorchange
87239              * Fires when the active error message is changed via {@link #setActiveError}.
87240              * @param {Ext.form.Labelable} this
87241              * @param {String} error The active error message
87242              */
87243             'errorchange'
87244         );
87245     },
87246
87247     /**
87248      * Returns the label for the field. Defaults to simply returning the {@link #fieldLabel} config. Can be
87249      * overridden to provide
87250      * @return {String} The configured field label, or empty string if not defined
87251      */
87252     getFieldLabel: function() {
87253         return this.fieldLabel || '';
87254     },
87255
87256     /**
87257      * @protected
87258      * Generates the arguments for the field decorations {@link #labelableRenderTpl rendering template}.
87259      * @return {Object} The template arguments
87260      */
87261     getLabelableRenderData: function() {
87262         var me = this,
87263             labelAlign = me.labelAlign,
87264             labelPad = me.labelPad,
87265             labelStyle;
87266
87267         // Calculate label styles up front rather than in the Field layout for speed; this
87268         // is safe because label alignment/width/pad are not expected to change.
87269         if (labelAlign === 'top') {
87270             labelStyle = 'margin-bottom:' + labelPad + 'px;';
87271         } else {
87272             labelStyle = 'margin-right:' + labelPad + 'px;';
87273             // Add the width for border-box browsers; will be set by the Field layout for content-box
87274             if (Ext.isBorderBox) {
87275                 labelStyle += 'width:' + me.labelWidth + 'px;';
87276             }
87277         }
87278
87279         return Ext.copyTo(
87280             {
87281                 inputId: me.getInputId(),
87282                 fieldLabel: me.getFieldLabel(),
87283                 labelStyle: labelStyle + (me.labelStyle || ''),
87284                 subTplMarkup: me.getSubTplMarkup()
87285             },
87286             me,
87287             'hideLabel,hideEmptyLabel,labelCls,fieldBodyCls,baseBodyCls,errorMsgCls,clearCls,labelSeparator',
87288             true
87289         );
87290     },
87291
87292     /**
87293      * @protected
87294      * Returns the additional {@link Ext.AbstractComponent#renderSelectors} for selecting the field
87295      * decoration elements from the rendered {@link #labelableRenderTpl}. Component classes using this mixin should
87296      * be sure and merge this method's result into the component's {@link Ext.AbstractComponent#renderSelectors}
87297      * before rendering.
87298      */
87299     getLabelableSelectors: function() {
87300         return {
87301             /**
87302              * @property labelEl
87303              * @type Ext.core.Element
87304              * The label Element for this component. Only available after the component has been rendered.
87305              */
87306             labelEl: 'label.' + this.labelCls,
87307
87308             /**
87309              * @property bodyEl
87310              * @type Ext.core.Element
87311              * The div Element wrapping the component's contents. Only available after the component has been rendered.
87312              */
87313             bodyEl: '.' + this.baseBodyCls,
87314
87315             /**
87316              * @property errorEl
87317              * @type Ext.core.Element
87318              * The div Element that will contain the component's error message(s). Note that depending on the
87319              * configured {@link #msgTarget}, this element may be hidden in favor of some other form of
87320              * presentation, but will always be present in the DOM for use by assistive technologies.
87321              */
87322             errorEl: '.' + this.errorMsgCls
87323         };
87324     },
87325
87326     /**
87327      * @protected
87328      * Gets the markup to be inserted into the outer template's bodyEl. Defaults to empty string, should
87329      * be implemented by classes including this mixin as needed.
87330      * @return {String} The markup to be inserted
87331      */
87332     getSubTplMarkup: function() {
87333         return '';
87334     },
87335
87336     /**
87337      * Get the input id, if any, for this component. This is used as the "for" attribute on the label element.
87338      * Implementing subclasses may also use this as e.g. the id for their own <tt>input</tt> element.
87339      * @return {String} The input id
87340      */
87341     getInputId: function() {
87342         return '';
87343     },
87344
87345     /**
87346      * Gets the active error message for this component, if any. This does not trigger
87347      * validation on its own, it merely returns any message that the component may already hold.
87348      * @return {String} The active error message on the component; if there is no error, an empty string is returned.
87349      */
87350     getActiveError : function() {
87351         return this.activeError || '';
87352     },
87353
87354     /**
87355      * Tells whether the field currently has an active error message. This does not trigger
87356      * validation on its own, it merely looks for any message that the component may already hold.
87357      * @return {Boolean}
87358      */
87359     hasActiveError: function() {
87360         return !!this.getActiveError();
87361     },
87362
87363     /**
87364      * Sets the active error message to the given string. This replaces the entire error message
87365      * contents with the given string. Also see {@link #setActiveErrors} which accepts an Array of
87366      * messages and formats them according to the {@link #activeErrorsTpl}.
87367      * @param {String} msg The error message
87368      */
87369     setActiveError: function(msg) {
87370         this.activeError = msg;
87371         this.activeErrors = [msg];
87372         this.renderActiveError();
87373     },
87374
87375     /**
87376      * Gets an Array of any active error messages currently applied to the field. This does not trigger
87377      * validation on its own, it merely returns any messages that the component may already hold.
87378      * @return {Array} The active error messages on the component; if there are no errors, an empty Array is returned.
87379      */
87380     getActiveErrors: function() {
87381         return this.activeErrors || [];
87382     },
87383
87384     /**
87385      * Set the active error message to an Array of error messages. The messages are formatted into
87386      * a single message string using the {@link #activeErrorsTpl}. Also see {@link #setActiveError}
87387      * which allows setting the entire error contents with a single string.
87388      * @param {Array} errors The error messages
87389      */
87390     setActiveErrors: function(errors) {
87391         this.activeErrors = errors;
87392         this.activeError = this.getTpl('activeErrorsTpl').apply({errors: errors});
87393         this.renderActiveError();
87394     },
87395
87396     /**
87397      * Clears the active error.
87398      */
87399     unsetActiveError: function() {
87400         delete this.activeError;
87401         delete this.activeErrors;
87402         this.renderActiveError();
87403     },
87404
87405     /**
87406      * @private
87407      * Updates the rendered DOM to match the current activeError. This only updates the content and
87408      * attributes, you'll have to call doComponentLayout to actually update the display.
87409      */
87410     renderActiveError: function() {
87411         var me = this,
87412             activeError = me.getActiveError(),
87413             hasError = !!activeError;
87414
87415         if (activeError !== me.lastActiveError) {
87416             me.fireEvent('errorchange', me, activeError);
87417             me.lastActiveError = activeError;
87418         }
87419
87420         if (me.rendered && !me.isDestroyed && !me.preventMark) {
87421             // Add/remove invalid class
87422             me.el[hasError ? 'addCls' : 'removeCls'](me.invalidCls);
87423
87424             // Update the aria-invalid attribute
87425             me.getActionEl().dom.setAttribute('aria-invalid', hasError);
87426
87427             // Update the errorEl with the error message text
87428             me.errorEl.dom.innerHTML = activeError;
87429         }
87430     },
87431
87432     /**
87433      * Applies a set of default configuration values to this Labelable instance. For each of the
87434      * properties in the given object, check if this component hasOwnProperty that config; if not
87435      * then it's inheriting a default value from its prototype and we should apply the default value.
87436      * @param {Object} defaults The defaults to apply to the object.
87437      */
87438     setFieldDefaults: function(defaults) {
87439         var me = this;
87440         Ext.iterate(defaults, function(key, val) {
87441             if (!me.hasOwnProperty(key)) {
87442                 me[key] = val;
87443             }
87444         });
87445     },
87446
87447     /**
87448      * @protected Calculate and return the natural width of the bodyEl. Override to provide custom logic.
87449      * Note for implementors: if at all possible this method should be overridden with a custom implementation
87450      * that can avoid anything that would cause the browser to reflow, e.g. querying offsetWidth.
87451      */
87452     getBodyNaturalWidth: function() {
87453         return this.bodyEl.getWidth();
87454     }
87455
87456 });
87457
87458 /**
87459  * @class Ext.form.field.Field
87460
87461 This mixin provides a common interface for the logical behavior and state of form fields, including:
87462
87463 - Getter and setter methods for field values
87464 - Events and methods for tracking value and validity changes
87465 - Methods for triggering validation
87466
87467 **NOTE**: When implementing custom fields, it is most likely that you will want to extend the {@link Ext.form.field.Base}
87468 component class rather than using this mixin directly, as BaseField contains additional logic for generating an
87469 actual DOM complete with {@link Ext.form.Labelable label and error message} display and a form input field,
87470 plus methods that bind the Field value getters and setters to the input field's value.
87471
87472 If you do want to implement this mixin directly and don't want to extend {@link Ext.form.field.Base}, then
87473 you will most likely want to override the following methods with custom implementations: {@link #getValue},
87474 {@link #setValue}, and {@link #getErrors}. Other methods may be overridden as needed but their base
87475 implementations should be sufficient for common cases. You will also need to make sure that {@link #initField}
87476 is called during the component's initialization.
87477
87478  * @markdown
87479  * @docauthor Jason Johnston <jason@sencha.com>
87480  */
87481 Ext.define('Ext.form.field.Field', {
87482
87483     /**
87484      * @property isFormField
87485      * @type {Boolean}
87486      * Flag denoting that this component is a Field. Always true.
87487      */
87488     isFormField : true,
87489
87490     /**
87491      * @cfg {Mixed} value A value to initialize this field with (defaults to undefined).
87492      */
87493     
87494     /**
87495      * @cfg {String} name The name of the field (defaults to undefined). By default this is used as the parameter
87496      * name when including the {@link #getSubmitData field value} in a {@link Ext.form.Basic#submit form submit()}.
87497      * To prevent the field from being included in the form submit, set {@link #submitValue} to <tt>false</tt>.
87498      */
87499
87500     /**
87501      * @cfg {Boolean} disabled True to disable the field (defaults to false). Disabled Fields will not be
87502      * {@link Ext.form.Basic#submit submitted}.</p>
87503      */
87504     disabled : false,
87505
87506     /**
87507      * @cfg {Boolean} submitValue Setting this to <tt>false</tt> will prevent the field from being
87508      * {@link Ext.form.Basic#submit submitted} even when it is not disabled. Defaults to <tt>true</tt>.
87509      */
87510     submitValue: true,
87511
87512     /**
87513      * @cfg {Boolean} validateOnChange
87514      * <p>Specifies whether this field should be validated immediately whenever a change in its value is detected.
87515      * Defaults to <tt>true</tt>. If the validation results in a change in the field's validity, a
87516      * {@link #validitychange} event will be fired. This allows the field to show feedback about the
87517      * validity of its contents immediately as the user is typing.</p>
87518      * <p>When set to <tt>false</tt>, feedback will not be immediate. However the form will still be validated
87519      * before submitting if the <tt>clientValidation</tt> option to {@link Ext.form.Basic#doAction} is
87520      * enabled, or if the field or form are validated manually.</p>
87521      * <p>See also {@link Ext.form.field.Base#checkChangeEvents}for controlling how changes to the field's value are detected.</p>
87522      */
87523     validateOnChange: true,
87524
87525     /**
87526      * @private
87527      */
87528     suspendCheckChange: 0,
87529
87530     /**
87531      * Initializes this Field mixin on the current instance. Components using this mixin should call
87532      * this method during their own initialization process.
87533      */
87534     initField: function() {
87535         this.addEvents(
87536             /**
87537              * @event change
87538              * Fires when a user-initiated change is detected in the value of the field.
87539              * @param {Ext.form.field.Field} this
87540              * @param {Mixed} newValue The new value
87541              * @param {Mixed} oldValue The original value
87542              */
87543             'change',
87544             /**
87545              * @event validitychange
87546              * Fires when a change in the field's validity is detected.
87547              * @param {Ext.form.field.Field} this
87548              * @param {Boolean} isValid Whether or not the field is now valid
87549              */
87550             'validitychange',
87551             /**
87552              * @event dirtychange
87553              * Fires when a change in the field's {@link #isDirty} state is detected.
87554              * @param {Ext.form.field.Field} this
87555              * @param {Boolean} isDirty Whether or not the field is now dirty
87556              */
87557             'dirtychange'
87558         );
87559
87560         this.initValue();
87561     },
87562
87563     /**
87564      * @protected
87565      * Initializes the field's value based on the initial config.
87566      */
87567     initValue: function() {
87568         var me = this;
87569
87570         /**
87571          * @property originalValue
87572          * @type Mixed
87573          * The original value of the field as configured in the {@link #value} configuration, or as loaded by
87574          * the last form load operation if the form's {@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad}
87575          * setting is <code>true</code>.
87576          */
87577         me.originalValue = me.lastValue = me.value;
87578
87579         // Set the initial value - prevent validation on initial set
87580         me.suspendCheckChange++;
87581         me.setValue(me.value);
87582         me.suspendCheckChange--;
87583     },
87584
87585     /**
87586      * Returns the {@link Ext.form.field.Field#name name} attribute of the field. This is used as the parameter
87587      * name when including the field value in a {@link Ext.form.Basic#submit form submit()}.
87588      * @return {String} name The field {@link Ext.form.field.Field#name name}
87589      */
87590     getName: function() {
87591         return this.name;
87592     },
87593
87594     /**
87595      * Returns the current data value of the field. The type of value returned is particular to the type of the
87596      * particular field (e.g. a Date object for {@link Ext.form.field.Date}).
87597      * @return {Mixed} value The field value
87598      */
87599     getValue: function() {
87600         return this.value;
87601     },
87602     
87603     /**
87604      * Sets a data value into the field and runs the change detection and validation.
87605      * @param {Mixed} value The value to set
87606      * @return {Ext.form.field.Field} this
87607      */
87608     setValue: function(value) {
87609         var me = this;
87610         me.value = value;
87611         me.checkChange();
87612         return me;
87613     },
87614
87615     /**
87616      * Returns whether two field {@link #getValue values} are logically equal. Field implementations may override
87617      * this to provide custom comparison logic appropriate for the particular field's data type.
87618      * @param {Mixed} value1 The first value to compare
87619      * @param {Mixed} value2 The second value to compare
87620      * @return {Boolean} True if the values are equal, false if inequal.
87621      */
87622     isEqual: function(value1, value2) {
87623         return String(value1) === String(value2);
87624     },
87625
87626     /**
87627      * <p>Returns the parameter(s) that would be included in a standard form submit for this field. Typically this
87628      * will be an object with a single name-value pair, the name being this field's {@link #getName name} and the
87629      * value being its current stringified value. More advanced field implementations may return more than one
87630      * name-value pair.</p>
87631      * <p>Note that the values returned from this method are not guaranteed to have been successfully
87632      * {@link #validate validated}.</p>
87633      * @return {Object} A mapping of submit parameter names to values; each value should be a string, or an array
87634      * of strings if that particular name has multiple values. It can also return <tt>null</tt> if there are no
87635      * parameters to be submitted.
87636      */
87637     getSubmitData: function() {
87638         var me = this,
87639             data = null;
87640         if (!me.disabled && me.submitValue && !me.isFileUpload()) {
87641             data = {};
87642             data[me.getName()] = '' + me.getValue();
87643         }
87644         return data;
87645     },
87646
87647     /**
87648      * <p>Returns the value(s) that should be saved to the {@link Ext.data.Model} instance for this field, when
87649      * {@link Ext.form.Basic#updateRecord} is called. Typically this will be an object with a single name-value
87650      * pair, the name being this field's {@link #getName name} and the value being its current data value. More
87651      * advanced field implementations may return more than one name-value pair. The returned values will be
87652      * saved to the corresponding field names in the Model.</p>
87653      * <p>Note that the values returned from this method are not guaranteed to have been successfully
87654      * {@link #validate validated}.</p>
87655      * @return {Object} A mapping of submit parameter names to values; each value should be a string, or an array
87656      * of strings if that particular name has multiple values. It can also return <tt>null</tt> if there are no
87657      * parameters to be submitted.
87658      */
87659     getModelData: function() {
87660         var me = this,
87661             data = null;
87662         if (!me.disabled && !me.isFileUpload()) {
87663             data = {};
87664             data[me.getName()] = me.getValue();
87665         }
87666         return data;
87667     },
87668
87669     /**
87670      * Resets the current field value to the originally loaded value and clears any validation messages.
87671      * See {@link Ext.form.Basic}.{@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad}
87672      */
87673     reset : function(){
87674         var me = this;
87675         
87676         me.setValue(me.originalValue);
87677         me.clearInvalid();
87678         // delete here so we reset back to the original state
87679         delete me.wasValid;
87680     },
87681
87682     /**
87683      * Resets the field's {@link #originalValue} property so it matches the current {@link #getValue value}.
87684      * This is called by {@link Ext.form.Basic}.{@link Ext.form.Basic#setValues setValues} if the form's
87685      * {@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad} property is set to true.
87686      */
87687     resetOriginalValue: function() {
87688         this.originalValue = this.getValue();
87689         this.checkDirty();
87690     },
87691
87692     /**
87693      * <p>Checks whether the value of the field has changed since the last time it was checked. If the value
87694      * has changed, it:</p>
87695      * <ol>
87696      * <li>Fires the {@link #change change event},</li>
87697      * <li>Performs validation if the {@link #validateOnChange} config is enabled, firing the
87698      * {@link #validationchange validationchange event} if the validity has changed, and</li>
87699      * <li>Checks the {@link #isDirty dirty state} of the field and fires the {@link #dirtychange dirtychange event}
87700      * if it has changed.</li>
87701      * </ol>
87702      */
87703     checkChange: function() {
87704         if (!this.suspendCheckChange) {
87705             var me = this,
87706                 newVal = me.getValue(),
87707                 oldVal = me.lastValue;
87708             if (!me.isEqual(newVal, oldVal) && !me.isDestroyed) {
87709                 me.lastValue = newVal;
87710                 me.fireEvent('change', me, newVal, oldVal);
87711                 me.onChange(newVal, oldVal);
87712             }
87713         }
87714     },
87715
87716     /**
87717      * @private
87718      * Called when the field's value changes. Performs validation if the {@link #validateOnChange}
87719      * config is enabled, and invokes the dirty check.
87720      */
87721     onChange: function(newVal, oldVal) {
87722         if (this.validateOnChange) {
87723             this.validate();
87724         }
87725         this.checkDirty();
87726     },
87727
87728     /**
87729      * <p>Returns true if the value of this Field has been changed from its {@link #originalValue}.
87730      * Will always return false if the field is disabled.</p>
87731      * <p>Note that if the owning {@link Ext.form.Basic form} was configured with
87732      * {@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad}
87733      * then the {@link #originalValue} is updated when the values are loaded by
87734      * {@link Ext.form.Basic}.{@link Ext.form.Basic#setValues setValues}.</p>
87735      * @return {Boolean} True if this field has been changed from its original value (and
87736      * is not disabled), false otherwise.
87737      */
87738     isDirty : function() {
87739         var me = this;
87740         return !me.disabled && !me.isEqual(me.getValue(), me.originalValue);
87741     },
87742
87743     /**
87744      * Checks the {@link #isDirty} state of the field and if it has changed since the last time
87745      * it was checked, fires the {@link #dirtychange} event.
87746      */
87747     checkDirty: function() {
87748         var me = this,
87749             isDirty = me.isDirty();
87750         if (isDirty !== me.wasDirty) {
87751             me.fireEvent('dirtychange', me, isDirty);
87752             me.onDirtyChange(isDirty);
87753             me.wasDirty = isDirty;
87754         }
87755     },
87756
87757     /**
87758      * @private Called when the field's dirty state changes.
87759      * @param {Boolean} isDirty
87760      */
87761     onDirtyChange: Ext.emptyFn,
87762
87763     /**
87764      * <p>Runs this field's validators and returns an array of error messages for any validation failures.
87765      * This is called internally during validation and would not usually need to be used manually.</p>
87766      * <p>Each subclass should override or augment the return value to provide their own errors.</p>
87767      * @param {Mixed} value The value to get errors for (defaults to the current field value)
87768      * @return {Array} All error messages for this field; an empty Array if none.
87769      */
87770     getErrors: function(value) {
87771         return [];
87772     },
87773
87774     /**
87775      * <p>Returns whether or not the field value is currently valid by {@link #getErrors validating} the
87776      * field's current value. The {@link #validitychange} event will not be fired; use {@link #validate}
87777      * instead if you want the event to fire. <b>Note</b>: {@link #disabled} fields are always treated as valid.</p>
87778      * <p>Implementations are encouraged to ensure that this method does not have side-effects such as
87779      * triggering error message display.</p>
87780      * @return {Boolean} True if the value is valid, else false
87781      */
87782     isValid : function() {
87783         var me = this;
87784         return me.disabled || Ext.isEmpty(me.getErrors());
87785     },
87786
87787     /**
87788      * <p>Returns whether or not the field value is currently valid by {@link #getErrors validating} the
87789      * field's current value, and fires the {@link #validitychange} event if the field's validity has
87790      * changed since the last validation. <b>Note</b>: {@link #disabled} fields are always treated as valid.</p>
87791      * <p>Custom implementations of this method are allowed to have side-effects such as triggering error
87792      * message display. To validate without side-effects, use {@link #isValid}.</p>
87793      * @return {Boolean} True if the value is valid, else false
87794      */
87795     validate : function() {
87796         var me = this,
87797             isValid = me.isValid();
87798         if (isValid !== me.wasValid) {
87799             me.wasValid = isValid;
87800             me.fireEvent('validitychange', me, isValid);
87801         }
87802         return isValid;
87803     },
87804
87805     /**
87806      * A utility for grouping a set of modifications which may trigger value changes into a single
87807      * transaction, to prevent excessive firing of {@link #change} events. This is useful for instance
87808      * if the field has sub-fields which are being updated as a group; you don't want the container
87809      * field to check its own changed state for each subfield change.
87810      * @param fn A function containing the transaction code
87811      */
87812     batchChanges: function(fn) {
87813         this.suspendCheckChange++;
87814         fn();
87815         this.suspendCheckChange--;
87816         this.checkChange();
87817     },
87818
87819     /**
87820      * Returns whether this Field is a file upload field; if it returns true, forms will use
87821      * special techniques for {@link Ext.form.Basic#submit submitting the form} via AJAX. See
87822      * {@link Ext.form.Basic#hasUpload} for details. If this returns true, the {@link #extractFileInput}
87823      * method must also be implemented to return the corresponding file input element.
87824      * @return {Boolean}
87825      */
87826     isFileUpload: function() {
87827         return false;
87828     },
87829
87830     /**
87831      * Only relevant if the instance's {@link #isFileUpload} method returns true. Returns a reference
87832      * to the file input DOM element holding the user's selected file. The input will be appended into
87833      * the submission form and will not be returned, so this method should also create a replacement.
87834      * @return {HTMLInputElement}
87835      */
87836     extractFileInput: function() {
87837         return null;
87838     },
87839
87840     /**
87841      * <p>Associate one or more error messages with this field. Components using this mixin should implement
87842      * this method to update the component's rendering to display the messages.</p>
87843      * <p><b>Note</b>: this method does not cause the Field's {@link #validate} or {@link #isValid} methods to
87844      * return <code>false</code> if the value does <i>pass</i> validation. So simply marking a Field as invalid
87845      * will not prevent submission of forms submitted with the {@link Ext.form.action.Submit#clientValidation}
87846      * option set.</p>
87847      * @param {String/Array} errors The error message(s) for the field.
87848      */
87849     markInvalid: Ext.emptyFn,
87850
87851     /**
87852      * <p>Clear any invalid styles/messages for this field. Components using this mixin should implement
87853      * this method to update the components rendering to clear any existing messages.</p>
87854      * <p><b>Note</b>: this method does not cause the Field's {@link #validate} or {@link #isValid} methods to
87855      * return <code>true</code> if the value does not <i>pass</i> validation. So simply clearing a field's errors
87856      * will not necessarily allow submission of forms submitted with the {@link Ext.form.action.Submit#clientValidation}
87857      * option set.</p>
87858      */
87859     clearInvalid: Ext.emptyFn
87860
87861 });
87862
87863 /**
87864  * @class Ext.layout.component.field.Field
87865  * @extends Ext.layout.component.Component
87866  * Layout class for components with {@link Ext.form.Labelable field labeling}, handling the sizing and alignment of
87867  * the form control, label, and error message treatment.
87868  * @private
87869  */
87870 Ext.define('Ext.layout.component.field.Field', {
87871
87872     /* Begin Definitions */
87873
87874     alias: ['layout.field'],
87875
87876     extend: 'Ext.layout.component.Component',
87877
87878     uses: ['Ext.tip.QuickTip', 'Ext.util.TextMetrics'],
87879
87880     /* End Definitions */
87881
87882     type: 'field',
87883
87884     beforeLayout: function(width, height) {
87885         var me = this;
87886         return me.callParent(arguments) || (!me.owner.preventMark && me.activeError !== me.owner.getActiveError());
87887     },
87888
87889     onLayout: function(width, height) {
87890         var me = this,
87891             owner = me.owner,
87892             labelStrategy = me.getLabelStrategy(),
87893             errorStrategy = me.getErrorStrategy(),
87894             isDefined = Ext.isDefined,
87895             isNumber = Ext.isNumber,
87896             lastSize, autoWidth, autoHeight, info, undef;
87897
87898         lastSize = me.lastComponentSize || {};
87899         if (!isDefined(width)) {
87900             width = lastSize.width;
87901             if (width < 0) { //first pass lastComponentSize.width is -Infinity
87902                 width = undef;
87903             }
87904         }
87905         if (!isDefined(height)) {
87906             height = lastSize.height;
87907             if (height < 0) { //first pass lastComponentSize.height is -Infinity
87908                 height = undef;
87909             }
87910         }
87911         autoWidth = !isNumber(width);
87912         autoHeight = !isNumber(height);
87913
87914         info = {
87915             autoWidth: autoWidth,
87916             autoHeight: autoHeight,
87917             width: autoWidth ? owner.getBodyNaturalWidth() : width, //always give a pixel width
87918             height: height,
87919
87920             // insets for the bodyEl from each side of the component layout area
87921             insets: {
87922                 top: 0,
87923                 right: 0,
87924                 bottom: 0,
87925                 left: 0
87926             }
87927         };
87928
87929         // NOTE the order of calculating insets and setting styles here is very important; we must first
87930         // calculate and set horizontal layout alone, as the horizontal sizing of elements can have an impact
87931         // on the vertical sizes due to wrapping, then calculate and set the vertical layout.
87932
87933         // perform preparation on the label and error (setting css classes, qtips, etc.)
87934         labelStrategy.prepare(owner, info);
87935         errorStrategy.prepare(owner, info);
87936
87937         // calculate the horizontal insets for the label and error
87938         labelStrategy.adjustHorizInsets(owner, info);
87939         errorStrategy.adjustHorizInsets(owner, info);
87940
87941         // set horizontal styles for label and error based on the current insets
87942         labelStrategy.layoutHoriz(owner, info);
87943         errorStrategy.layoutHoriz(owner, info);
87944
87945         // calculate the vertical insets for the label and error
87946         labelStrategy.adjustVertInsets(owner, info);
87947         errorStrategy.adjustVertInsets(owner, info);
87948
87949         // set vertical styles for label and error based on the current insets
87950         labelStrategy.layoutVert(owner, info);
87951         errorStrategy.layoutVert(owner, info);
87952
87953         // perform sizing of the elements based on the final dimensions and insets
87954         if (autoWidth && autoHeight) {
87955             // Don't use setTargetSize if auto-sized, so the calculated size is not reused next time
87956             me.setElementSize(owner.el, info.width, info.height);
87957         } else {
87958             me.setTargetSize(info.width, info.height);
87959         }
87960         me.sizeBody(info);
87961
87962         me.activeError = owner.getActiveError();
87963     },
87964
87965
87966     /**
87967      * Perform sizing and alignment of the bodyEl (and children) to match the calculated insets.
87968      */
87969     sizeBody: function(info) {
87970         var me = this,
87971             owner = me.owner,
87972             insets = info.insets,
87973             totalWidth = info.width,
87974             totalHeight = info.height,
87975             width = Ext.isNumber(totalWidth) ? totalWidth - insets.left - insets.right : totalWidth,
87976             height = Ext.isNumber(totalHeight) ? totalHeight - insets.top - insets.bottom : totalHeight;
87977
87978         // size the bodyEl
87979         me.setElementSize(owner.bodyEl, width, height);
87980
87981         // size the bodyEl's inner contents if necessary
87982         me.sizeBodyContents(width, height);
87983     },
87984
87985     /**
87986      * Size the contents of the field body, given the full dimensions of the bodyEl. Does nothing by
87987      * default, subclasses can override to handle their specific contents.
87988      * @param {Number} width The bodyEl width
87989      * @param {Number} height The bodyEl height
87990      */
87991     sizeBodyContents: Ext.emptyFn,
87992
87993
87994     /**
87995      * Return the set of strategy functions from the {@link #labelStrategies labelStrategies collection}
87996      * that is appropriate for the field's {@link Ext.form.field.Field#labelAlign labelAlign} config.
87997      */
87998     getLabelStrategy: function() {
87999         var me = this,
88000             strategies = me.labelStrategies,
88001             labelAlign = me.owner.labelAlign;
88002         return strategies[labelAlign] || strategies.base;
88003     },
88004
88005     /**
88006      * Return the set of strategy functions from the {@link #errorStrategies errorStrategies collection}
88007      * that is appropriate for the field's {@link Ext.form.field.Field#msgTarget msgTarget} config.
88008      */
88009     getErrorStrategy: function() {
88010         var me = this,
88011             owner = me.owner,
88012             strategies = me.errorStrategies,
88013             msgTarget = owner.msgTarget;
88014         return !owner.preventMark && Ext.isString(msgTarget) ?
88015                 (strategies[msgTarget] || strategies.elementId) :
88016                 strategies.none;
88017     },
88018
88019
88020
88021     /**
88022      * Collection of named strategies for laying out and adjusting labels to accommodate error messages.
88023      * An appropriate one will be chosen based on the owner field's {@link Ext.form.field.Field#labelAlign} config.
88024      */
88025     labelStrategies: (function() {
88026         var applyIf = Ext.applyIf,
88027             emptyFn = Ext.emptyFn,
88028             base = {
88029                 prepare: function(owner, info) {
88030                     var cls = owner.labelCls + '-' + owner.labelAlign,
88031                         labelEl = owner.labelEl;
88032                     if (labelEl && !labelEl.hasCls(cls)) {
88033                         labelEl.addCls(cls);
88034                     }
88035                 },
88036                 adjustHorizInsets: emptyFn,
88037                 adjustVertInsets: emptyFn,
88038                 layoutHoriz: emptyFn,
88039                 layoutVert: emptyFn
88040             },
88041             left = applyIf({
88042                 prepare: function(owner, info) {
88043                     base.prepare(owner, info);
88044                     // If auto width, add the label width to the body's natural width.
88045                     if (info.autoWidth) {
88046                         info.width += (!owner.labelEl ? 0 : owner.labelWidth + owner.labelPad);
88047                     }
88048                 },
88049                 adjustHorizInsets: function(owner, info) {
88050                     if (owner.labelEl) {
88051                         info.insets.left += owner.labelWidth + owner.labelPad;
88052                     }
88053                 },
88054                 layoutHoriz: function(owner, info) {
88055                     // For content-box browsers we can't rely on Labelable.js#getLabelableRenderData
88056                     // setting the width style because it needs to account for the final calculated
88057                     // padding/border styles for the label. So we set the width programmatically here to
88058                     // normalize content-box sizing, while letting border-box browsers use the original
88059                     // width style.
88060                     var labelEl = owner.labelEl;
88061                     if (labelEl && !owner.isLabelSized && !Ext.isBorderBox) {
88062                         labelEl.setWidth(owner.labelWidth);
88063                         owner.isLabelSized = true;
88064                     }
88065                 }
88066             }, base);
88067
88068
88069         return {
88070             base: base,
88071
88072             /**
88073              * Label displayed above the bodyEl
88074              */
88075             top: applyIf({
88076                 adjustVertInsets: function(owner, info) {
88077                     var labelEl = owner.labelEl;
88078                     if (labelEl) {
88079                         info.insets.top += Ext.util.TextMetrics.measure(labelEl, owner.fieldLabel, info.width).height +
88080                                            labelEl.getFrameWidth('tb') + owner.labelPad;
88081                     }
88082                 }
88083             }, base),
88084
88085             /**
88086              * Label displayed to the left of the bodyEl
88087              */
88088             left: left,
88089
88090             /**
88091              * Same as left, only difference is text-align in CSS
88092              */
88093             right: left
88094         };
88095     })(),
88096
88097
88098
88099     /**
88100      * Collection of named strategies for laying out and adjusting insets to accommodate error messages.
88101      * An appropriate one will be chosen based on the owner field's {@link Ext.form.field.Field#msgTarget} config.
88102      */
88103     errorStrategies: (function() {
88104         function setDisplayed(el, displayed) {
88105             var wasDisplayed = el.getStyle('display') !== 'none';
88106             if (displayed !== wasDisplayed) {
88107                 el.setDisplayed(displayed);
88108             }
88109         }
88110
88111         function setStyle(el, name, value) {
88112             if (el.getStyle(name) !== value) {
88113                 el.setStyle(name, value);
88114             }
88115         }
88116
88117         var applyIf = Ext.applyIf,
88118             emptyFn = Ext.emptyFn,
88119             base = {
88120                 prepare: function(owner) {
88121                     setDisplayed(owner.errorEl, false);
88122                 },
88123                 adjustHorizInsets: emptyFn,
88124                 adjustVertInsets: emptyFn,
88125                 layoutHoriz: emptyFn,
88126                 layoutVert: emptyFn
88127             };
88128
88129         return {
88130             none: base,
88131
88132             /**
88133              * Error displayed as icon (with QuickTip on hover) to right of the bodyEl
88134              */
88135             side: applyIf({
88136                 prepare: function(owner) {
88137                     var errorEl = owner.errorEl;
88138                     errorEl.addCls(Ext.baseCSSPrefix + 'form-invalid-icon');
88139                     Ext.layout.component.field.Field.initTip();
88140                     errorEl.dom.setAttribute('data-errorqtip', owner.getActiveError() || '');
88141                     setDisplayed(errorEl, owner.hasActiveError());
88142                 },
88143                 adjustHorizInsets: function(owner, info) {
88144                     if (owner.autoFitErrors && owner.hasActiveError()) {
88145                         info.insets.right += owner.errorEl.getWidth();
88146                     }
88147                 },
88148                 layoutHoriz: function(owner, info) {
88149                     if (owner.hasActiveError()) {
88150                         setStyle(owner.errorEl, 'left', info.width - info.insets.right + 'px');
88151                     }
88152                 },
88153                 layoutVert: function(owner, info) {
88154                     if (owner.hasActiveError()) {
88155                         setStyle(owner.errorEl, 'top', info.insets.top + 'px');
88156                     }
88157                 }
88158             }, base),
88159
88160             /**
88161              * Error message displayed underneath the bodyEl
88162              */
88163             under: applyIf({
88164                 prepare: function(owner) {
88165                     var errorEl = owner.errorEl,
88166                         cls = Ext.baseCSSPrefix + 'form-invalid-under';
88167                     if (!errorEl.hasCls(cls)) {
88168                         errorEl.addCls(cls);
88169                     }
88170                     setDisplayed(errorEl, owner.hasActiveError());
88171                 },
88172                 adjustVertInsets: function(owner, info) {
88173                     if (owner.autoFitErrors) {
88174                         info.insets.bottom += owner.errorEl.getHeight();
88175                     }
88176                 },
88177                 layoutHoriz: function(owner, info) {
88178                     var errorEl = owner.errorEl,
88179                         insets = info.insets;
88180
88181                     setStyle(errorEl, 'width', info.width - insets.right - insets.left + 'px');
88182                     setStyle(errorEl, 'marginLeft', insets.left + 'px');
88183                 }
88184             }, base),
88185
88186             /**
88187              * Error displayed as QuickTip on hover of the field container
88188              */
88189             qtip: applyIf({
88190                 prepare: function(owner) {
88191                     setDisplayed(owner.errorEl, false);
88192                     Ext.layout.component.field.Field.initTip();
88193                     owner.getActionEl().dom.setAttribute('data-errorqtip', owner.getActiveError() || '');
88194                 }
88195             }, base),
88196
88197             /**
88198              * Error displayed as title tip on hover of the field container
88199              */
88200             title: applyIf({
88201                 prepare: function(owner) {
88202                     setDisplayed(owner.errorEl, false);
88203                     owner.el.dom.title = owner.getActiveError() || '';
88204                 }
88205             }, base),
88206
88207             /**
88208              * Error message displayed as content of an element with a given id elsewhere in the app
88209              */
88210             elementId: applyIf({
88211                 prepare: function(owner) {
88212                     setDisplayed(owner.errorEl, false);
88213                     var targetEl = Ext.fly(owner.msgTarget);
88214                     if (targetEl) {
88215                         targetEl.dom.innerHTML = owner.getActiveError() || '';
88216                         targetEl.setDisplayed(owner.hasActiveError());
88217                     }
88218                 }
88219             }, base)
88220         };
88221     })(),
88222
88223     statics: {
88224         /**
88225          * Use a custom QuickTip instance separate from the main QuickTips singleton, so that we
88226          * can give it a custom frame style. Responds to errorqtip rather than the qtip property.
88227          */
88228         initTip: function() {
88229             var tip = this.tip;
88230             if (!tip) {
88231                 tip = this.tip = Ext.create('Ext.tip.QuickTip', {
88232                     baseCls: Ext.baseCSSPrefix + 'form-invalid-tip',
88233                     renderTo: Ext.getBody()
88234                 });
88235                 tip.tagConfig = Ext.apply({}, {attribute: 'errorqtip'}, tip.tagConfig);
88236             }
88237         },
88238
88239         /**
88240          * Destroy the error tip instance.
88241          */
88242         destroyTip: function() {
88243             var tip = this.tip;
88244             if (tip) {
88245                 tip.destroy();
88246                 delete this.tip;
88247             }
88248         }
88249     }
88250
88251 });
88252
88253 /**
88254  * @class Ext.form.field.VTypes
88255  * <p>This is a singleton object which contains a set of commonly used field validation functions.
88256  * The validations provided are basic and intended to be easily customizable and extended.</p>
88257  * <p>To add custom VTypes specify the <code>{@link Ext.form.field.Text#vtype vtype}</code> validation
88258  * test function, and optionally specify any corresponding error text to display and any keystroke
88259  * filtering mask to apply. For example:</p>
88260  * <pre><code>
88261 // custom Vtype for vtype:'time'
88262 var timeTest = /^([1-9]|1[0-9]):([0-5][0-9])(\s[a|p]m)$/i;
88263 Ext.apply(Ext.form.field.VTypes, {
88264     //  vtype validation function
88265     time: function(val, field) {
88266         return timeTest.test(val);
88267     },
88268     // vtype Text property: The error text to display when the validation function returns false
88269     timeText: 'Not a valid time.  Must be in the format "12:34 PM".',
88270     // vtype Mask property: The keystroke filter mask
88271     timeMask: /[\d\s:amp]/i
88272 });
88273  * </code></pre>
88274  * Another example:
88275  * <pre><code>
88276 // custom Vtype for vtype:'IPAddress'
88277 Ext.apply(Ext.form.field.VTypes, {
88278     IPAddress:  function(v) {
88279         return /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(v);
88280     },
88281     IPAddressText: 'Must be a numeric IP address',
88282     IPAddressMask: /[\d\.]/i
88283 });
88284  * </code></pre>
88285  * @singleton
88286  */
88287 Ext.define('Ext.form.field.VTypes', (function(){
88288     // closure these in so they are only created once.
88289     var alpha = /^[a-zA-Z_]+$/,
88290         alphanum = /^[a-zA-Z0-9_]+$/,
88291         email = /^(\w+)([\-+.][\w]+)*@(\w[\-\w]*\.){1,5}([A-Za-z]){2,6}$/,
88292         url = /(((^https?)|(^ftp)):\/\/([\-\w]+\.)+\w{2,3}(\/[%\-\w]+(\.\w{2,})?)*(([\w\-\.\?\\\/+@&#;`~=%!]*)(\.\w{2,})?)*\/?)/i;
88293
88294     // All these messages and functions are configurable
88295     return {
88296         singleton: true,
88297         alternateClassName: 'Ext.form.VTypes',
88298
88299         /**
88300          * The function used to validate email addresses.  Note that this is a very basic validation -- complete
88301          * validation per the email RFC specifications is very complex and beyond the scope of this class, although
88302          * this function can be overridden if a more comprehensive validation scheme is desired.  See the validation
88303          * section of the <a href="http://en.wikipedia.org/wiki/E-mail_address">Wikipedia article on email addresses</a>
88304          * for additional information.  This implementation is intended to validate the following emails:<tt>
88305          * 'barney@example.de', 'barney.rubble@example.com', 'barney-rubble@example.coop', 'barney+rubble@example.com'
88306          * </tt>.
88307          * @param {String} value The email address
88308          * @return {Boolean} true if the RegExp test passed, and false if not.
88309          */
88310         'email' : function(v){
88311             return email.test(v);
88312         },
88313         /**
88314          * The error text to display when the email validation function returns false.  Defaults to:
88315          * <tt>'This field should be an e-mail address in the format "user@example.com"'</tt>
88316          * @type String
88317          */
88318         'emailText' : 'This field should be an e-mail address in the format "user@example.com"',
88319         /**
88320          * The keystroke filter mask to be applied on email input.  See the {@link #email} method for
88321          * information about more complex email validation. Defaults to:
88322          * <tt>/[a-z0-9_\.\-@]/i</tt>
88323          * @type RegExp
88324          */
88325         'emailMask' : /[a-z0-9_\.\-@\+]/i,
88326
88327         /**
88328          * The function used to validate URLs
88329          * @param {String} value The URL
88330          * @return {Boolean} true if the RegExp test passed, and false if not.
88331          */
88332         'url' : function(v){
88333             return url.test(v);
88334         },
88335         /**
88336          * The error text to display when the url validation function returns false.  Defaults to:
88337          * <tt>'This field should be a URL in the format "http:/'+'/www.example.com"'</tt>
88338          * @type String
88339          */
88340         'urlText' : 'This field should be a URL in the format "http:/'+'/www.example.com"',
88341
88342         /**
88343          * The function used to validate alpha values
88344          * @param {String} value The value
88345          * @return {Boolean} true if the RegExp test passed, and false if not.
88346          */
88347         'alpha' : function(v){
88348             return alpha.test(v);
88349         },
88350         /**
88351          * The error text to display when the alpha validation function returns false.  Defaults to:
88352          * <tt>'This field should only contain letters and _'</tt>
88353          * @type String
88354          */
88355         'alphaText' : 'This field should only contain letters and _',
88356         /**
88357          * The keystroke filter mask to be applied on alpha input.  Defaults to:
88358          * <tt>/[a-z_]/i</tt>
88359          * @type RegExp
88360          */
88361         'alphaMask' : /[a-z_]/i,
88362
88363         /**
88364          * The function used to validate alphanumeric values
88365          * @param {String} value The value
88366          * @return {Boolean} true if the RegExp test passed, and false if not.
88367          */
88368         'alphanum' : function(v){
88369             return alphanum.test(v);
88370         },
88371         /**
88372          * The error text to display when the alphanumeric validation function returns false.  Defaults to:
88373          * <tt>'This field should only contain letters, numbers and _'</tt>
88374          * @type String
88375          */
88376         'alphanumText' : 'This field should only contain letters, numbers and _',
88377         /**
88378          * The keystroke filter mask to be applied on alphanumeric input.  Defaults to:
88379          * <tt>/[a-z0-9_]/i</tt>
88380          * @type RegExp
88381          */
88382         'alphanumMask' : /[a-z0-9_]/i
88383     };
88384 })());
88385
88386 /**
88387  * @private
88388  * @class Ext.layout.component.field.Text
88389  * @extends Ext.layout.component.field.Field
88390  * Layout class for {@link Ext.form.field.Text} fields. Handles sizing the input field.
88391  */
88392 Ext.define('Ext.layout.component.field.Text', {
88393     extend: 'Ext.layout.component.field.Field',
88394     alias: 'layout.textfield',
88395     requires: ['Ext.util.TextMetrics'],
88396
88397     type: 'textfield',
88398
88399
88400     /**
88401      * Allow layout to proceed if the {@link Ext.form.field.Text#grow} config is enabled and the value has
88402      * changed since the last layout.
88403      */
88404     beforeLayout: function(width, height) {
88405         var me = this,
88406             owner = me.owner,
88407             lastValue = this.lastValue,
88408             value = owner.getRawValue();
88409         this.lastValue = value;
88410         return me.callParent(arguments) || (owner.grow && value !== lastValue);
88411     },
88412
88413
88414     /**
88415      * Size the field body contents given the total dimensions of the bodyEl, taking into account the optional
88416      * {@link Ext.form.field.Text#grow} configurations.
88417      * @param {Number} width The bodyEl width
88418      * @param {Number} height The bodyEl height
88419      */
88420     sizeBodyContents: function(width, height) {
88421         var size = this.adjustForGrow(width, height);
88422         this.setElementSize(this.owner.inputEl, size[0], size[1]);
88423     },
88424
88425
88426     /**
88427      * Given the target bodyEl dimensions, adjust them if necessary to return the correct final
88428      * size based on the text field's {@link Ext.form.field.Text#grow grow config}.
88429      * @param {Number} width The bodyEl width
88430      * @param {Number} height The bodyEl height
88431      * @return {Array} [inputElWidth, inputElHeight]
88432      */
88433     adjustForGrow: function(width, height) {
88434         var me = this,
88435             owner = me.owner,
88436             inputEl, value, calcWidth,
88437             result = [width, height];
88438
88439         if (owner.grow) {
88440             inputEl = owner.inputEl;
88441
88442             // Find the width that contains the whole text value
88443             value = (inputEl.dom.value || (owner.hasFocus ? '' : owner.emptyText) || '') + owner.growAppend;
88444             calcWidth = inputEl.getTextWidth(value) + inputEl.getBorderWidth("lr") + inputEl.getPadding("lr");
88445
88446             // Constrain
88447             result[0] = Ext.Number.constrain(calcWidth, owner.growMin,
88448                     Math.max(owner.growMin, Math.min(owner.growMax, Ext.isNumber(width) ? width : Infinity)));
88449         }
88450
88451         return result;
88452     }
88453
88454 });
88455
88456 /**
88457  * @private
88458  * @class Ext.layout.component.field.TextArea
88459  * @extends Ext.layout.component.field.Field
88460  * Layout class for {@link Ext.form.field.TextArea} fields. Handles sizing the textarea field.
88461  */
88462 Ext.define('Ext.layout.component.field.TextArea', {
88463     extend: 'Ext.layout.component.field.Text',
88464     alias: 'layout.textareafield',
88465
88466     type: 'textareafield',
88467
88468
88469     /**
88470      * Given the target bodyEl dimensions, adjust them if necessary to return the correct final
88471      * size based on the text field's {@link Ext.form.field.Text#grow grow config}. Overrides the
88472      * textfield layout's implementation to handle height rather than width.
88473      * @param {Number} width The bodyEl width
88474      * @param {Number} height The bodyEl height
88475      * @return {Array} [inputElWidth, inputElHeight]
88476      */
88477     adjustForGrow: function(width, height) {
88478         var me = this,
88479             owner = me.owner,
88480             inputEl, value, max,
88481             curWidth, curHeight, calcHeight,
88482             result = [width, height];
88483
88484         if (owner.grow) {
88485             inputEl = owner.inputEl;
88486             curWidth = inputEl.getWidth(true); //subtract border/padding to get the available width for the text
88487             curHeight = inputEl.getHeight();
88488
88489             // Get and normalize the field value for measurement
88490             value = inputEl.dom.value || '&#160;';
88491             value += owner.growAppend;
88492
88493             // Translate newlines to <br> tags
88494             value = value.replace(/\n/g, '<br>');
88495
88496             // Find the height that contains the whole text value
88497             calcHeight = Ext.util.TextMetrics.measure(inputEl, value, curWidth).height +
88498                          inputEl.getBorderWidth("tb") + inputEl.getPadding("tb");
88499
88500             // Constrain
88501             max = owner.growMax;
88502             if (Ext.isNumber(height)) {
88503                 max = Math.min(max, height);
88504             }
88505             result[1] = Ext.Number.constrain(calcHeight, owner.growMin, max);
88506         }
88507
88508         return result;
88509     }
88510
88511 });
88512 /**
88513  * @class Ext.layout.container.Anchor
88514  * @extends Ext.layout.container.Container
88515  * <p>This is a layout that enables anchoring of contained elements relative to the container's dimensions.
88516  * If the container is resized, all anchored items are automatically rerendered according to their
88517  * <b><tt>{@link #anchor}</tt></b> rules.</p>
88518  * <p>This class is intended to be extended or created via the layout: 'anchor' {@link Ext.layout.container.AbstractContainer#layout}
88519  * config, and should generally not need to be created directly via the new keyword.</p>
88520  * <p>AnchorLayout does not have any direct config options (other than inherited ones). By default,
88521  * AnchorLayout will calculate anchor measurements based on the size of the container itself. However, the
88522  * container using the AnchorLayout can supply an anchoring-specific config property of <b>anchorSize</b>.
88523  * If anchorSize is specifed, the layout will use it as a virtual container for the purposes of calculating
88524  * anchor measurements based on it instead, allowing the container to be sized independently of the anchoring
88525  * logic if necessary.  
88526  * {@img Ext.layout.container.Anchor/Ext.layout.container.Anchor.png Ext.layout.container.Anchor container layout}
88527  * For example:
88528         Ext.create('Ext.Panel', {
88529                 width: 500,
88530                 height: 400,
88531                 title: "AnchorLayout Panel",
88532                 layout: 'anchor',
88533                 renderTo: Ext.getBody(),
88534                 items: [{
88535                         xtype: 'panel',
88536                         title: '75% Width and 20% Height',
88537                         anchor: '75% 20%'
88538                 },{
88539                         xtype: 'panel',
88540                         title: 'Offset -300 Width & -200 Height',
88541                         anchor: '-300 -200'             
88542                 },{
88543                         xtype: 'panel',
88544                         title: 'Mixed Offset and Percent',
88545                         anchor: '-250 20%'
88546                 }]
88547         });
88548  */
88549
88550 Ext.define('Ext.layout.container.Anchor', {
88551
88552     /* Begin Definitions */
88553
88554     alias: 'layout.anchor',
88555     extend: 'Ext.layout.container.Container',
88556     alternateClassName: 'Ext.layout.AnchorLayout',
88557
88558     /* End Definitions */
88559
88560     /**
88561      * @cfg {String} anchor
88562      * <p>This configuation option is to be applied to <b>child <tt>items</tt></b> of a container managed by
88563      * this layout (ie. configured with <tt>layout:'anchor'</tt>).</p><br/>
88564      *
88565      * <p>This value is what tells the layout how an item should be anchored to the container. <tt>items</tt>
88566      * added to an AnchorLayout accept an anchoring-specific config property of <b>anchor</b> which is a string
88567      * containing two values: the horizontal anchor value and the vertical anchor value (for example, '100% 50%').
88568      * The following types of anchor values are supported:<div class="mdetail-params"><ul>
88569      *
88570      * <li><b>Percentage</b> : Any value between 1 and 100, expressed as a percentage.<div class="sub-desc">
88571      * The first anchor is the percentage width that the item should take up within the container, and the
88572      * second is the percentage height.  For example:<pre><code>
88573 // two values specified
88574 anchor: '100% 50%' // render item complete width of the container and
88575                    // 1/2 height of the container
88576 // one value specified
88577 anchor: '100%'     // the width value; the height will default to auto
88578      * </code></pre></div></li>
88579      *
88580      * <li><b>Offsets</b> : Any positive or negative integer value.<div class="sub-desc">
88581      * This is a raw adjustment where the first anchor is the offset from the right edge of the container,
88582      * and the second is the offset from the bottom edge. For example:<pre><code>
88583 // two values specified
88584 anchor: '-50 -100' // render item the complete width of the container
88585                    // minus 50 pixels and
88586                    // the complete height minus 100 pixels.
88587 // one value specified
88588 anchor: '-50'      // anchor value is assumed to be the right offset value
88589                    // bottom offset will default to 0
88590      * </code></pre></div></li>
88591      *
88592      * <li><b>Sides</b> : Valid values are <tt>'right'</tt> (or <tt>'r'</tt>) and <tt>'bottom'</tt>
88593      * (or <tt>'b'</tt>).<div class="sub-desc">
88594      * Either the container must have a fixed size or an anchorSize config value defined at render time in
88595      * order for these to have any effect.</div></li>
88596      *
88597      * <li><b>Mixed</b> : <div class="sub-desc">
88598      * Anchor values can also be mixed as needed.  For example, to render the width offset from the container
88599      * right edge by 50 pixels and 75% of the container's height use:
88600      * <pre><code>
88601 anchor: '-50 75%'
88602      * </code></pre></div></li>
88603      *
88604      *
88605      * </ul></div>
88606      */
88607
88608     type: 'anchor',
88609
88610     /**
88611      * @cfg {String} defaultAnchor
88612      *
88613      * default anchor for all child container items applied if no anchor or specific width is set on the child item.  Defaults to '100%'.
88614      *
88615      */
88616     defaultAnchor: '100%',
88617
88618     parseAnchorRE: /^(r|right|b|bottom)$/i,
88619
88620     // private
88621     onLayout: function() {
88622         this.callParent(arguments);
88623
88624         var me = this,
88625             size = me.getLayoutTargetSize(),
88626             owner = me.owner,
88627             target = me.getTarget(),
88628             ownerWidth = size.width,
88629             ownerHeight = size.height,
88630             overflow = target.getStyle('overflow'),
88631             components = me.getVisibleItems(owner),
88632             len = components.length,
88633             boxes = [],
88634             box, newTargetSize, anchorWidth, anchorHeight, component, anchorSpec, calcWidth, calcHeight,
88635             anchorsArray, anchor, i, el;
88636
88637         if (ownerWidth < 20 && ownerHeight < 20) {
88638             return;
88639         }
88640
88641         // Anchor layout uses natural HTML flow to arrange the child items.
88642         // To ensure that all browsers (I'm looking at you IE!) add the bottom margin of the last child to the
88643         // containing element height, we create a zero-sized element with style clear:both to force a "new line"
88644         if (!me.clearEl) {
88645             me.clearEl = target.createChild({
88646                 cls: Ext.baseCSSPrefix + 'clear',
88647                 role: 'presentation'
88648             });
88649         }
88650
88651         // find the container anchoring size
88652         if (owner.anchorSize) {
88653             if (typeof owner.anchorSize == 'number') {
88654                 anchorWidth = owner.anchorSize;
88655             }
88656             else {
88657                 anchorWidth = owner.anchorSize.width;
88658                 anchorHeight = owner.anchorSize.height;
88659             }
88660         }
88661         else {
88662             anchorWidth = owner.initialConfig.width;
88663             anchorHeight = owner.initialConfig.height;
88664         }
88665
88666         // Work around WebKit RightMargin bug. We're going to inline-block all the children only ONCE and remove it when we're done
88667         if (!Ext.supports.RightMargin) {
88668             target.addCls(Ext.baseCSSPrefix + 'inline-children');
88669         }
88670
88671         for (i = 0; i < len; i++) {
88672             component = components[i];
88673             el = component.el;
88674             anchor = component.anchor;
88675
88676             if (!component.anchor && component.items && !Ext.isNumber(component.width) && !(Ext.isIE6 && Ext.isStrict)) {
88677                 component.anchor = anchor = me.defaultAnchor;
88678             }
88679
88680             if (anchor) {
88681                 anchorSpec = component.anchorSpec;
88682                 // cache all anchor values
88683                 if (!anchorSpec) {
88684                     anchorsArray = anchor.split(' ');
88685                     component.anchorSpec = anchorSpec = {
88686                         right: me.parseAnchor(anchorsArray[0], component.initialConfig.width, anchorWidth),
88687                         bottom: me.parseAnchor(anchorsArray[1], component.initialConfig.height, anchorHeight)
88688                     };
88689                 }
88690                 calcWidth = anchorSpec.right ? me.adjustWidthAnchor(anchorSpec.right(ownerWidth) - el.getMargin('lr'), component) : undefined;
88691                 calcHeight = anchorSpec.bottom ? me.adjustHeightAnchor(anchorSpec.bottom(ownerHeight) - el.getMargin('tb'), component) : undefined;
88692
88693                 boxes.push({
88694                     component: component,
88695                     anchor: true,
88696                     width: calcWidth || undefined,
88697                     height: calcHeight || undefined
88698                 });
88699             } else {
88700                 boxes.push({
88701                     component: component,
88702                     anchor: false
88703                 });
88704             }
88705         }
88706
88707         // Work around WebKit RightMargin bug. We're going to inline-block all the children only ONCE and remove it when we're done
88708         if (!Ext.supports.RightMargin) {
88709             target.removeCls(Ext.baseCSSPrefix + 'inline-children');
88710         }
88711
88712         for (i = 0; i < len; i++) {
88713             box = boxes[i];
88714             me.setItemSize(box.component, box.width, box.height);
88715         }
88716
88717         if (overflow && overflow != 'hidden' && !me.adjustmentPass) {
88718             newTargetSize = me.getLayoutTargetSize();
88719             if (newTargetSize.width != size.width || newTargetSize.height != size.height) {
88720                 me.adjustmentPass = true;
88721                 me.onLayout();
88722             }
88723         }
88724
88725         delete me.adjustmentPass;
88726     },
88727
88728     // private
88729     parseAnchor: function(a, start, cstart) {
88730         if (a && a != 'none') {
88731             var ratio;
88732             // standard anchor
88733             if (this.parseAnchorRE.test(a)) {
88734                 var diff = cstart - start;
88735                 return function(v) {
88736                     return v - diff;
88737                 };
88738             }    
88739             // percentage
88740             else if (a.indexOf('%') != -1) {
88741                 ratio = parseFloat(a.replace('%', '')) * 0.01;
88742                 return function(v) {
88743                     return Math.floor(v * ratio);
88744                 };
88745             }    
88746             // simple offset adjustment
88747             else {
88748                 a = parseInt(a, 10);
88749                 if (!isNaN(a)) {
88750                     return function(v) {
88751                         return v + a;
88752                     };
88753                 }
88754             }
88755         }
88756         return null;
88757     },
88758
88759     // private
88760     adjustWidthAnchor: function(value, comp) {
88761         return value;
88762     },
88763
88764     // private
88765     adjustHeightAnchor: function(value, comp) {
88766         return value;
88767     }
88768
88769 });
88770 /**
88771  * @class Ext.form.action.Load
88772  * @extends Ext.form.action.Action
88773  * <p>A class which handles loading of data from a server into the Fields of an {@link Ext.form.Basic}.</p>
88774  * <p>Instances of this class are only created by a {@link Ext.form.Basic Form} when
88775  * {@link Ext.form.Basic#load load}ing.</p>
88776  * <p><u><b>Response Packet Criteria</b></u></p>
88777  * <p>A response packet <b>must</b> contain:
88778  * <div class="mdetail-params"><ul>
88779  * <li><b><code>success</code></b> property : Boolean</li>
88780  * <li><b><code>data</code></b> property : Object</li>
88781  * <div class="sub-desc">The <code>data</code> property contains the values of Fields to load.
88782  * The individual value object for each Field is passed to the Field's
88783  * {@link Ext.form.field.Field#setValue setValue} method.</div></li>
88784  * </ul></div>
88785  * <p><u><b>JSON Packets</b></u></p>
88786  * <p>By default, response packets are assumed to be JSON, so for the following form load call:<pre><code>
88787 var myFormPanel = new Ext.form.Panel({
88788     title: 'Client and routing info',
88789     items: [{
88790         fieldLabel: 'Client',
88791         name: 'clientName'
88792     }, {
88793         fieldLabel: 'Port of loading',
88794         name: 'portOfLoading'
88795     }, {
88796         fieldLabel: 'Port of discharge',
88797         name: 'portOfDischarge'
88798     }]
88799 });
88800 myFormPanel.{@link Ext.form.Panel#getForm getForm}().{@link Ext.form.Basic#load load}({
88801     url: '/getRoutingInfo.php',
88802     params: {
88803         consignmentRef: myConsignmentRef
88804     },
88805     failure: function(form, action) {
88806         Ext.Msg.alert("Load failed", action.result.errorMessage);
88807     }
88808 });
88809 </code></pre>
88810  * a <b>success response</b> packet may look like this:</p><pre><code>
88811 {
88812     success: true,
88813     data: {
88814         clientName: "Fred. Olsen Lines",
88815         portOfLoading: "FXT",
88816         portOfDischarge: "OSL"
88817     }
88818 }</code></pre>
88819  * while a <b>failure response</b> packet may look like this:</p><pre><code>
88820 {
88821     success: false,
88822     errorMessage: "Consignment reference not found"
88823 }</code></pre>
88824  * <p>Other data may be placed into the response for processing the {@link Ext.form.Basic Form}'s
88825  * callback or event handler methods. The object decoded from this JSON is available in the
88826  * {@link Ext.form.action.Action#result result} property.</p>
88827  */
88828 Ext.define('Ext.form.action.Load', {
88829     extend:'Ext.form.action.Action',
88830     requires: ['Ext.data.Connection'],
88831     alternateClassName: 'Ext.form.Action.Load',
88832     alias: 'formaction.load',
88833
88834     type: 'load',
88835
88836     /**
88837      * @private
88838      */
88839     run: function() {
88840         Ext.Ajax.request(Ext.apply(
88841             this.createCallback(),
88842             {
88843                 method: this.getMethod(),
88844                 url: this.getUrl(),
88845                 headers: this.headers,
88846                 params: this.getParams()
88847             }
88848         ));
88849     },
88850
88851     /**
88852      * @private
88853      */
88854     onSuccess: function(response){
88855         var result = this.processResponse(response),
88856             form = this.form;
88857         if (result === true || !result.success || !result.data) {
88858             this.failureType = Ext.form.action.Action.LOAD_FAILURE;
88859             form.afterAction(this, false);
88860             return;
88861         }
88862         form.clearInvalid();
88863         form.setValues(result.data);
88864         form.afterAction(this, true);
88865     },
88866
88867     /**
88868      * @private
88869      */
88870     handleResponse: function(response) {
88871         var reader = this.form.reader,
88872             rs, data;
88873         if (reader) {
88874             rs = reader.read(response);
88875             data = rs.records && rs.records[0] ? rs.records[0].data : null;
88876             return {
88877                 success : rs.success,
88878                 data : data
88879             };
88880         }
88881         return Ext.decode(response.responseText);
88882     }
88883 });
88884
88885
88886 /**
88887  * @class Ext.window.Window
88888  * @extends Ext.panel.Panel
88889  * <p>A specialized panel intended for use as an application window.  Windows are floated, {@link #resizable}, and
88890  * {@link #draggable} by default.  Windows can be {@link #maximizable maximized} to fill the viewport,
88891  * restored to their prior size, and can be {@link #minimize}d.</p>
88892  * <p>Windows can also be linked to a {@link Ext.ZIndexManager} or managed by the {@link Ext.WindowManager} to provide
88893  * grouping, activation, to front, to back and other application-specific behavior.</p>
88894  * <p>By default, Windows will be rendered to document.body. To {@link #constrain} a Window to another element
88895  * specify {@link Ext.Component#renderTo renderTo}.</p>
88896  * <p><b>As with all {@link Ext.container.Container Container}s, it is important to consider how you want the Window
88897  * to size and arrange any child Components. Choose an appropriate {@link #layout} configuration which lays out
88898  * child Components in the required manner.</b></p>
88899  * {@img Ext.window.Window/Ext.window.Window.png Window component}
88900  * Example:<code><pre>
88901 Ext.create('Ext.window.Window', {
88902     title: 'Hello',
88903     height: 200,
88904     width: 400,
88905     layout: 'fit',
88906     items: {  // Let's put an empty grid in just to illustrate fit layout
88907         xtype: 'grid',
88908         border: false,
88909         columns: [{header: 'World'}],                 // One header just for show. There's no data,
88910         store: Ext.create('Ext.data.ArrayStore', {}) // A dummy empty data store
88911     }
88912 }).show();
88913 </pre></code>
88914  * @constructor
88915  * @param {Object} config The config object
88916  * @xtype window
88917  */
88918 Ext.define('Ext.window.Window', {
88919     extend: 'Ext.panel.Panel',
88920
88921     alternateClassName: 'Ext.Window',
88922
88923     requires: ['Ext.util.ComponentDragger', 'Ext.util.Region', 'Ext.EventManager'],
88924
88925     alias: 'widget.window',
88926
88927     /**
88928      * @cfg {Number} x
88929      * The X position of the left edge of the window on initial showing. Defaults to centering the Window within
88930      * the width of the Window's container {@link Ext.core.Element Element) (The Element that the Window is rendered to).
88931      */
88932     /**
88933      * @cfg {Number} y
88934      * The Y position of the top edge of the window on initial showing. Defaults to centering the Window within
88935      * the height of the Window's container {@link Ext.core.Element Element) (The Element that the Window is rendered to).
88936      */
88937     /**
88938      * @cfg {Boolean} modal
88939      * True to make the window modal and mask everything behind it when displayed, false to display it without
88940      * restricting access to other UI elements (defaults to false).
88941      */
88942     /**
88943      * @cfg {String/Element} animateTarget
88944      * Id or element from which the window should animate while opening (defaults to null with no animation).
88945      */
88946     /**
88947      * @cfg {String/Number/Component} defaultFocus
88948      * <p>Specifies a Component to receive focus when this Window is focused.</p>
88949      * <p>This may be one of:</p><div class="mdetail-params"><ul>
88950      * <li>The index of a footer Button.</li>
88951      * <li>The id or {@link Ext.AbstractComponent#itemId} of a descendant Component.</li>
88952      * <li>A Component.</li>
88953      * </ul></div>
88954      */
88955     /**
88956      * @cfg {Function} onEsc
88957      * Allows override of the built-in processing for the escape key. Default action
88958      * is to close the Window (performing whatever action is specified in {@link #closeAction}.
88959      * To prevent the Window closing when the escape key is pressed, specify this as
88960      * Ext.emptyFn (See {@link Ext#emptyFn Ext.emptyFn}).
88961      */
88962     /**
88963      * @cfg {Boolean} collapsed
88964      * True to render the window collapsed, false to render it expanded (defaults to false). Note that if
88965      * {@link #expandOnShow} is true (the default) it will override the <code>collapsed</code> config and the window
88966      * will always be expanded when shown.
88967      */
88968     /**
88969      * @cfg {Boolean} maximized
88970      * True to initially display the window in a maximized state. (Defaults to false).
88971      */
88972
88973     /**
88974     * @cfg {String} baseCls
88975     * The base CSS class to apply to this panel's element (defaults to 'x-window').
88976     */
88977     baseCls: Ext.baseCSSPrefix + 'window',
88978
88979     /**
88980      * @cfg {Mixed} resizable
88981      * <p>Specify as <code>true</code> to allow user resizing at each edge and corner of the window, false to disable
88982      * resizing (defaults to true).</p>
88983      * <p>This may also be specified as a config object to </p>
88984      */
88985     resizable: true,
88986
88987     /**
88988      * @cfg {Boolean} draggable
88989      * <p>True to allow the window to be dragged by the header bar, false to disable dragging (defaults to true).  Note
88990      * that by default the window will be centered in the viewport, so if dragging is disabled the window may need
88991      * to be positioned programmatically after render (e.g., myWindow.setPosition(100, 100);).<p>
88992      */
88993     draggable: true,
88994
88995     /**
88996      * @cfg {Boolean} constrain
88997      * True to constrain the window within its containing element, false to allow it to fall outside of its
88998      * containing element. By default the window will be rendered to document.body.  To render and constrain the
88999      * window within another element specify {@link #renderTo}.
89000      * (defaults to false).  Optionally the header only can be constrained using {@link #constrainHeader}.
89001      */
89002     constrain: false,
89003
89004     /**
89005      * @cfg {Boolean} constrainHeader
89006      * True to constrain the window header within its containing element (allowing the window body to fall outside
89007      * of its containing element) or false to allow the header to fall outside its containing element (defaults to
89008      * false). Optionally the entire window can be constrained using {@link #constrain}.
89009      */
89010     constrainHeader: false,
89011
89012     /**
89013      * @cfg {Boolean} plain
89014      * True to render the window body with a transparent background so that it will blend into the framing
89015      * elements, false to add a lighter background color to visually highlight the body element and separate it
89016      * more distinctly from the surrounding frame (defaults to false).
89017      */
89018     plain: false,
89019
89020     /**
89021      * @cfg {Boolean} minimizable
89022      * True to display the 'minimize' tool button and allow the user to minimize the window, false to hide the button
89023      * and disallow minimizing the window (defaults to false).  Note that this button provides no implementation --
89024      * the behavior of minimizing a window is implementation-specific, so the minimize event must be handled and a
89025      * custom minimize behavior implemented for this option to be useful.
89026      */
89027     minimizable: false,
89028
89029     /**
89030      * @cfg {Boolean} maximizable
89031      * True to display the 'maximize' tool button and allow the user to maximize the window, false to hide the button
89032      * and disallow maximizing the window (defaults to false).  Note that when a window is maximized, the tool button
89033      * will automatically change to a 'restore' button with the appropriate behavior already built-in that will
89034      * restore the window to its previous size.
89035      */
89036     maximizable: false,
89037
89038     // inherit docs
89039     minHeight: 100,
89040
89041     // inherit docs
89042     minWidth: 200,
89043
89044     /**
89045      * @cfg {Boolean} expandOnShow
89046      * True to always expand the window when it is displayed, false to keep it in its current state (which may be
89047      * {@link #collapsed}) when displayed (defaults to true).
89048      */
89049     expandOnShow: true,
89050
89051     // inherited docs, same default
89052     collapsible: false,
89053
89054     /**
89055      * @cfg {Boolean} closable
89056      * <p>True to display the 'close' tool button and allow the user to close the window, false to
89057      * hide the button and disallow closing the window (defaults to <code>true</code>).</p>
89058      * <p>By default, when close is requested by either clicking the close button in the header
89059      * or pressing ESC when the Window has focus, the {@link #close} method will be called. This
89060      * will <i>{@link Ext.Component#destroy destroy}</i> the Window and its content meaning that
89061      * it may not be reused.</p>
89062      * <p>To make closing a Window <i>hide</i> the Window so that it may be reused, set
89063      * {@link #closeAction} to 'hide'.</p>
89064      */
89065     closable: true,
89066
89067     /**
89068      * @cfg {Boolean} hidden
89069      * Render this Window hidden (default is <code>true</code>). If <code>true</code>, the
89070      * {@link #hide} method will be called internally.
89071      */
89072     hidden: true,
89073
89074     // Inherit docs from Component. Windows render to the body on first show.
89075     autoRender: true,
89076
89077     // Inherit docs from Component. Windows hide using visibility.
89078     hideMode: 'visibility',
89079
89080     /** @cfg {Boolean} floating @hide Windows are always floating*/
89081     floating: true,
89082
89083     ariaRole: 'alertdialog',
89084     
89085     itemCls: 'x-window-item',
89086
89087     overlapHeader: true,
89088     
89089     ignoreHeaderBorderManagement: true,
89090
89091     // private
89092     initComponent: function() {
89093         var me = this;
89094         me.callParent();
89095         me.addEvents(
89096             /**
89097              * @event activate
89098              * Fires after the window has been visually activated via {@link #setActive}.
89099              * @param {Ext.window.Window} this
89100              */
89101             /**
89102              * @event deactivate
89103              * Fires after the window has been visually deactivated via {@link #setActive}.
89104              * @param {Ext.window.Window} this
89105              */
89106             /**
89107              * @event resize
89108              * Fires after the window has been resized.
89109              * @param {Ext.window.Window} this
89110              * @param {Number} width The window's new width
89111              * @param {Number} height The window's new height
89112              */
89113             'resize',
89114             /**
89115              * @event maximize
89116              * Fires after the window has been maximized.
89117              * @param {Ext.window.Window} this
89118              */
89119             'maximize',
89120             /**
89121              * @event minimize
89122              * Fires after the window has been minimized.
89123              * @param {Ext.window.Window} this
89124              */
89125             'minimize',
89126             /**
89127              * @event restore
89128              * Fires after the window has been restored to its original size after being maximized.
89129              * @param {Ext.window.Window} this
89130              */
89131             'restore'
89132         );
89133
89134         if (me.plain) {
89135             me.addClsWithUI('plain');
89136         }
89137
89138         if (me.modal) {
89139             me.ariaRole = 'dialog';
89140         }
89141     },
89142
89143     // State Management
89144     // private
89145
89146     initStateEvents: function(){
89147         var events = this.stateEvents;
89148         // push on stateEvents if they don't exist
89149         Ext.each(['maximize', 'restore', 'resize', 'dragend'], function(event){
89150             if (Ext.Array.indexOf(events, event)) {
89151                 events.push(event);
89152             }
89153         });
89154         this.callParent();
89155     },
89156
89157     getState: function() {
89158         var me = this,
89159             state = me.callParent() || {},
89160             maximized = !!me.maximized;
89161
89162         state.maximized = maximized;
89163         Ext.apply(state, {
89164             size: maximized ? me.restoreSize : me.getSize(),
89165             pos: maximized ? me.restorePos : me.getPosition()
89166         });
89167         return state;
89168     },
89169
89170     applyState: function(state){
89171         var me = this;
89172
89173         if (state) {
89174             me.maximized = state.maximized;
89175             if (me.maximized) {
89176                 me.hasSavedRestore = true;
89177                 me.restoreSize = state.size;
89178                 me.restorePos = state.pos;
89179             } else {
89180                 Ext.apply(me, {
89181                     width: state.size.width,
89182                     height: state.size.height,
89183                     x: state.pos[0],
89184                     y: state.pos[1]
89185                 });
89186             }
89187         }
89188     },
89189
89190     // private
89191     onMouseDown: function () {
89192         if (this.floating) {
89193             this.toFront();
89194         }
89195     },
89196
89197     // private
89198     onRender: function(ct, position) {
89199         var me = this;
89200         me.callParent(arguments);
89201         me.focusEl = me.el;
89202
89203         // Double clicking a header will toggleMaximize
89204         if (me.maximizable) {
89205             me.header.on({
89206                 dblclick: {
89207                     fn: me.toggleMaximize,
89208                     element: 'el',
89209                     scope: me
89210                 }
89211             });
89212         }
89213     },
89214
89215     // private
89216     afterRender: function() {
89217         var me = this,
89218             hidden = me.hidden,
89219             keyMap;
89220
89221         me.hidden = false;
89222         // Component's afterRender sizes and positions the Component
89223         me.callParent();
89224         me.hidden = hidden;
89225
89226         // Create the proxy after the size has been applied in Component.afterRender
89227         me.proxy = me.getProxy();
89228
89229         // clickToRaise
89230         me.mon(me.el, 'mousedown', me.onMouseDown, me);
89231
89232         // Initialize
89233         if (me.maximized) {
89234             me.maximized = false;
89235             me.maximize();
89236         }
89237
89238         if (me.closable) {
89239             keyMap = me.getKeyMap();
89240             keyMap.on(27, me.onEsc, me);
89241             keyMap.disable();
89242         }
89243     },
89244
89245     /**
89246      * @private
89247      * @override
89248      * Override Component.initDraggable.
89249      * Window uses the header element as the delegate.
89250      */
89251     initDraggable: function() {
89252         var me = this,
89253             ddConfig;
89254
89255         if (!me.header) {
89256             me.updateHeader(true);
89257         }
89258
89259         ddConfig = Ext.applyIf({
89260             el: me.el,
89261             delegate: '#' + me.header.id
89262         }, me.draggable);
89263
89264         // Add extra configs if Window is specified to be constrained
89265         if (me.constrain || me.constrainHeader) {
89266             ddConfig.constrain = me.constrain;
89267             ddConfig.constrainDelegate = me.constrainHeader;
89268             ddConfig.constrainTo = me.constrainTo || me.container;
89269         }
89270
89271         /**
89272          * <p>If this Window is configured {@link #draggable}, this property will contain
89273          * an instance of {@link Ext.util.ComponentDragger} (A subclass of {@link Ext.dd.DragTracker DragTracker})
89274          * which handles dragging the Window's DOM Element, and constraining according to the {@link #constrain}
89275          * and {@link #constrainHeader} .</p>
89276          * <p>This has implementations of <code>onBeforeStart</code>, <code>onDrag</code> and <code>onEnd</code>
89277          * which perform the dragging action. If extra logic is needed at these points, use
89278          * {@link Ext.Function#createInterceptor createInterceptor} or {@link Ext.Function#createSequence createSequence} to
89279          * augment the existing implementations.</p>
89280          * @type Ext.util.ComponentDragger
89281          * @property dd
89282          */
89283         me.dd = Ext.create('Ext.util.ComponentDragger', this, ddConfig);
89284         me.relayEvents(me.dd, ['dragstart', 'drag', 'dragend']);
89285     },
89286
89287     // private
89288     onEsc: function(k, e) {
89289         e.stopEvent();
89290         this[this.closeAction]();
89291     },
89292
89293     // private
89294     beforeDestroy: function() {
89295         var me = this;
89296         if (me.rendered) {
89297             delete this.animateTarget;
89298             me.hide();
89299             Ext.destroy(
89300                 me.keyMap
89301             );
89302         }
89303         me.callParent();
89304     },
89305
89306     /**
89307      * @private
89308      * @override
89309      * Contribute class-specific tools to the header.
89310      * Called by Panel's initTools.
89311      */
89312     addTools: function() {
89313         var me = this;
89314
89315         // Call Panel's initTools
89316         me.callParent();
89317
89318         if (me.minimizable) {
89319             me.addTool({
89320                 type: 'minimize',
89321                 handler: Ext.Function.bind(me.minimize, me, [])
89322             });
89323         }
89324         if (me.maximizable) {
89325             me.addTool({
89326                 type: 'maximize',
89327                 handler: Ext.Function.bind(me.maximize, me, [])
89328             });
89329             me.addTool({
89330                 type: 'restore',
89331                 handler: Ext.Function.bind(me.restore, me, []),
89332                 hidden: true
89333             });
89334         }
89335     },
89336
89337     /**
89338      * Gets the configured default focus item.  If a {@link #defaultFocus} is set, it will receive focus, otherwise the
89339      * Container itself will receive focus.
89340      */
89341     getFocusEl: function() {
89342         var me = this,
89343             f = me.focusEl,
89344             defaultComp = me.defaultButton || me.defaultFocus,
89345             t = typeof db,
89346             el,
89347             ct;
89348
89349         if (Ext.isDefined(defaultComp)) {
89350             if (Ext.isNumber(defaultComp)) {
89351                 f = me.query('button')[defaultComp];
89352             } else if (Ext.isString(defaultComp)) {
89353                 f = me.down('#' + defaultComp);
89354             } else {
89355                 f = defaultComp;
89356             }
89357         }
89358         return f || me.focusEl;
89359     },
89360
89361     // private
89362     beforeShow: function() {
89363         this.callParent();
89364
89365         if (this.expandOnShow) {
89366             this.expand(false);
89367         }
89368     },
89369
89370     // private
89371     afterShow: function(animateTarget) {
89372         var me = this,
89373             size;
89374
89375         // Perform superclass's afterShow tasks
89376         // Which might include animating a proxy from an animTarget
89377         me.callParent(arguments);
89378
89379         if (me.maximized) {
89380             me.fitContainer();
89381         }
89382
89383         if (me.monitorResize || me.constrain || me.constrainHeader) {
89384             Ext.EventManager.onWindowResize(me.onWindowResize, me);
89385         }
89386         me.doConstrain();
89387         if (me.keyMap) {
89388             me.keyMap.enable();
89389         }
89390     },
89391
89392     // private
89393     doClose: function() {
89394         var me = this;
89395
89396         // immediate close
89397         if (me.hidden) {
89398             me.fireEvent('close', me);
89399             me[me.closeAction]();
89400         } else {
89401             // close after hiding
89402             me.hide(me.animTarget, me.doClose, me);
89403         }
89404     },
89405
89406     // private
89407     afterHide: function() {
89408         var me = this;
89409
89410         // No longer subscribe to resizing now that we're hidden
89411         if (me.monitorResize || me.constrain || me.constrainHeader) {
89412             Ext.EventManager.removeResizeListener(me.onWindowResize, me);
89413         }
89414
89415         // Turn off keyboard handling once window is hidden
89416         if (me.keyMap) {
89417             me.keyMap.disable();
89418         }
89419
89420         // Perform superclass's afterHide tasks.
89421         me.callParent(arguments);
89422     },
89423
89424     // private
89425     onWindowResize: function() {
89426         if (this.maximized) {
89427             this.fitContainer();
89428         }
89429         this.doConstrain();
89430     },
89431
89432     /**
89433      * Placeholder method for minimizing the window.  By default, this method simply fires the {@link #minimize} event
89434      * since the behavior of minimizing a window is application-specific.  To implement custom minimize behavior,
89435      * either the minimize event can be handled or this method can be overridden.
89436      * @return {Ext.window.Window} this
89437      */
89438     minimize: function() {
89439         this.fireEvent('minimize', this);
89440         return this;
89441     },
89442
89443     afterCollapse: function() {
89444         var me = this;
89445
89446         if (me.maximizable) {
89447             me.tools.maximize.hide();
89448             me.tools.restore.hide();
89449         }
89450         if (me.resizer) {
89451             me.resizer.disable();
89452         }
89453         me.callParent(arguments);
89454     },
89455
89456     afterExpand: function() {
89457         var me = this;
89458
89459         if (me.maximized) {
89460             me.tools.restore.show();
89461         } else if (me.maximizable) {
89462             me.tools.maximize.show();
89463         }
89464         if (me.resizer) {
89465             me.resizer.enable();
89466         }
89467         me.callParent(arguments);
89468     },
89469
89470     /**
89471      * Fits the window within its current container and automatically replaces
89472      * the {@link #maximizable 'maximize' tool button} with the 'restore' tool button.
89473      * Also see {@link #toggleMaximize}.
89474      * @return {Ext.window.Window} this
89475      */
89476     maximize: function() {
89477         var me = this;
89478
89479         if (!me.maximized) {
89480             me.expand(false);
89481             if (!me.hasSavedRestore) {
89482                 me.restoreSize = me.getSize();
89483                 me.restorePos = me.getPosition(true);
89484             }
89485             if (me.maximizable) {
89486                 me.tools.maximize.hide();
89487                 me.tools.restore.show();
89488             }
89489             me.maximized = true;
89490             me.el.disableShadow();
89491
89492             if (me.dd) {
89493                 me.dd.disable();
89494             }
89495             if (me.collapseTool) {
89496                 me.collapseTool.hide();
89497             }
89498             me.el.addCls(Ext.baseCSSPrefix + 'window-maximized');
89499             me.container.addCls(Ext.baseCSSPrefix + 'window-maximized-ct');
89500
89501             me.setPosition(0, 0);
89502             me.fitContainer();
89503             me.fireEvent('maximize', me);
89504         }
89505         return me;
89506     },
89507
89508     /**
89509      * Restores a {@link #maximizable maximized}  window back to its original
89510      * size and position prior to being maximized and also replaces
89511      * the 'restore' tool button with the 'maximize' tool button.
89512      * Also see {@link #toggleMaximize}.
89513      * @return {Ext.window.Window} this
89514      */
89515     restore: function() {
89516         var me = this,
89517             tools = me.tools;
89518
89519         if (me.maximized) {
89520             delete me.hasSavedRestore;
89521             me.removeCls(Ext.baseCSSPrefix + 'window-maximized');
89522
89523             // Toggle tool visibility
89524             if (tools.restore) {
89525                 tools.restore.hide();
89526             }
89527             if (tools.maximize) {
89528                 tools.maximize.show();
89529             }
89530             if (me.collapseTool) {
89531                 me.collapseTool.show();
89532             }
89533
89534             // Restore the position/sizing
89535             me.setPosition(me.restorePos);
89536             me.setSize(me.restoreSize);
89537
89538             // Unset old position/sizing
89539             delete me.restorePos;
89540             delete me.restoreSize;
89541
89542             me.maximized = false;
89543
89544             me.el.enableShadow(true);
89545
89546             // Allow users to drag and drop again
89547             if (me.dd) {
89548                 me.dd.enable();
89549             }
89550
89551             me.container.removeCls(Ext.baseCSSPrefix + 'window-maximized-ct');
89552
89553             me.doConstrain();
89554             me.fireEvent('restore', me);
89555         }
89556         return me;
89557     },
89558
89559     /**
89560      * A shortcut method for toggling between {@link #maximize} and {@link #restore} based on the current maximized
89561      * state of the window.
89562      * @return {Ext.window.Window} this
89563      */
89564     toggleMaximize: function() {
89565         return this[this.maximized ? 'restore': 'maximize']();
89566     }
89567
89568     /**
89569      * @cfg {Boolean} autoWidth @hide
89570      * Absolute positioned element and therefore cannot support autoWidth.
89571      * A width is a required configuration.
89572      **/
89573 });
89574 /**
89575  * @class Ext.form.field.Base
89576  * @extends Ext.Component
89577
89578 Base class for form fields that provides default event handling, rendering, and other common functionality
89579 needed by all form field types. Utilizes the {@link Ext.form.field.Field} mixin for value handling and validation,
89580 and the {@link Ext.form.Labelable} mixin to provide label and error message display.
89581
89582 In most cases you will want to use a subclass, such as {@link Ext.form.field.Text} or {@link Ext.form.field.Checkbox},
89583 rather than creating instances of this class directly. However if you are implementing a custom form field,
89584 using this as the parent class is recommended.
89585
89586 __Values and Conversions__
89587
89588 Because BaseField implements the Field mixin, it has a main value that can be initialized with the
89589 {@link #value} config and manipulated via the {@link #getValue} and {@link #setValue} methods. This main
89590 value can be one of many data types appropriate to the current field, for instance a {@link Ext.form.field.Date Date}
89591 field would use a JavaScript Date object as its value type. However, because the field is rendered as a HTML
89592 input, this value data type can not always be directly used in the rendered field.
89593
89594 Therefore BaseField introduces the concept of a "raw value". This is the value of the rendered HTML input field,
89595 and is normally a String. The {@link #getRawValue} and {@link #setRawValue} methods can be used to directly
89596 work with the raw value, though it is recommended to use getValue and setValue in most cases.
89597
89598 Conversion back and forth between the main value and the raw value is handled by the {@link #valueToRaw} and
89599 {@link #rawToValue} methods. If you are implementing a subclass that uses a non-String value data type, you
89600 should override these methods to handle the conversion.
89601
89602 __Rendering__
89603
89604 The content of the field body is defined by the {@link #fieldSubTpl} XTemplate, with its argument data
89605 created by the {@link #getSubTplData} method. Override this template and/or method to create custom
89606 field renderings.
89607 {@img Ext.form.BaseField/Ext.form.BaseField.png Ext.form.BaseField BaseField component}
89608 __Example usage:__
89609
89610     // A simple subclass of BaseField that creates a HTML5 search field. Redirects to the
89611     // searchUrl when the Enter key is pressed.
89612     Ext.define('Ext.form.SearchField', {
89613         extend: 'Ext.form.field.Base',
89614         alias: 'widget.searchfield',
89615     
89616         inputType: 'search',
89617     
89618         // Config defining the search URL
89619         searchUrl: 'http://www.google.com/search?q={0}',
89620     
89621         // Add specialkey listener
89622         initComponent: function() {
89623             this.callParent();
89624             this.on('specialkey', this.checkEnterKey, this);
89625         },
89626     
89627         // Handle enter key presses, execute the search if the field has a value
89628         checkEnterKey: function(field, e) {
89629             var value = this.getValue();
89630             if (e.getKey() === e.ENTER && !Ext.isEmpty(value)) {
89631                 location.href = Ext.String.format(this.searchUrl, value);
89632             }
89633         }
89634     });
89635
89636     Ext.create('Ext.form.Panel', {
89637         title: 'BaseField Example',
89638         bodyPadding: 5,
89639         width: 250,
89640                 
89641         // Fields will be arranged vertically, stretched to full width
89642         layout: 'anchor',
89643         defaults: {
89644             anchor: '100%'
89645         },
89646         items: [{
89647             xtype: 'searchfield',
89648             fieldLabel: 'Search',
89649             name: 'query'
89650         }]
89651         renderTo: Ext.getBody()
89652     });
89653
89654  * @constructor
89655  * Creates a new Field
89656  * @param {Object} config Configuration options
89657  *
89658  * @xtype field
89659  * @markdown
89660  * @docauthor Jason Johnston <jason@sencha.com>
89661  */
89662 Ext.define('Ext.form.field.Base', {
89663     extend: 'Ext.Component',
89664     mixins: {
89665         labelable: 'Ext.form.Labelable',
89666         field: 'Ext.form.field.Field'
89667     },
89668     alias: 'widget.field',
89669     alternateClassName: ['Ext.form.Field', 'Ext.form.BaseField'],
89670     requires: ['Ext.util.DelayedTask', 'Ext.XTemplate', 'Ext.layout.component.field.Field'],
89671
89672     fieldSubTpl: [
89673         '<input id="{id}" type="{type}" ',
89674         '<tpl if="name">name="{name}" </tpl>',
89675         '<tpl if="size">size="{size}" </tpl>',
89676         '<tpl if="tabIdx">tabIndex="{tabIdx}" </tpl>',
89677         'class="{fieldCls} {typeCls}" autocomplete="off" />',
89678         {
89679             compiled: true,
89680             disableFormats: true
89681         }
89682     ],
89683
89684     /**
89685      * @cfg {String} name The name of the field (defaults to undefined). This is used as the parameter
89686      * name when including the field value in a {@link Ext.form.Basic#submit form submit()}. If no name is
89687      * configured, it falls back to the {@link #inputId}. To prevent the field from being included in the
89688      * form submit, set {@link #submitValue} to <tt>false</tt>.
89689      */
89690
89691     /**
89692      * @cfg {String} inputType
89693      * <p>The type attribute for input fields -- e.g. radio, text, password, file (defaults to <tt>'text'</tt>).
89694      * The extended types supported by HTML5 inputs (url, email, etc.) may also be used, though using them
89695      * will cause older browsers to fall back to 'text'.</p>
89696      * <p>The type 'password' must be used to render that field type currently -- there is no separate Ext
89697      * component for that. You can use {@link Ext.form.field.File} which creates a custom-rendered file upload
89698      * field, but if you want a plain unstyled file input you can use a BaseField with inputType:'file'.</p>
89699      */
89700     inputType: 'text',
89701
89702     /**
89703      * @cfg {Number} tabIndex The tabIndex for this field. Note this only applies to fields that are rendered,
89704      * not those which are built via applyTo (defaults to undefined).
89705      */
89706
89707     /**
89708      * @cfg {String} invalidText The error text to use when marking a field invalid and no message is provided
89709      * (defaults to 'The value in this field is invalid')
89710      */
89711     invalidText : 'The value in this field is invalid',
89712
89713     /**
89714      * @cfg {String} fieldCls The default CSS class for the field input (defaults to 'x-form-field')
89715      */
89716     fieldCls : Ext.baseCSSPrefix + 'form-field',
89717
89718     /**
89719      * @cfg {String} fieldStyle Optional CSS style(s) to be applied to the {@link #inputEl field input element}.
89720      * Should be a valid argument to {@link Ext.core.Element#applyStyles}. Defaults to undefined. See also the
89721      * {@link #setFieldStyle} method for changing the style after initialization.
89722      */
89723
89724     /**
89725      * @cfg {String} focusCls The CSS class to use when the field receives focus (defaults to 'x-form-focus')
89726      */
89727     focusCls : Ext.baseCSSPrefix + 'form-focus',
89728
89729     /**
89730      * @cfg {String} dirtyCls The CSS class to use when the field value {@link #isDirty is dirty}.
89731      */
89732     dirtyCls : Ext.baseCSSPrefix + 'form-dirty',
89733
89734     /**
89735      * @cfg {Array} checkChangeEvents
89736      * <p>A list of event names that will be listened for on the field's {@link #inputEl input element}, which
89737      * will cause the field's value to be checked for changes. If a change is detected, the
89738      * {@link #change change event} will be fired, followed by validation if the {@link #validateOnChange}
89739      * option is enabled.</p>
89740      * <p>Defaults to <tt>['change', 'propertychange']</tt> in Internet Explorer, and <tt>['change', 'input',
89741      * 'textInput', 'keyup', 'dragdrop']</tt> in other browsers. This catches all the ways that field values
89742      * can be changed in most supported browsers; the only known exceptions at the time of writing are:</p>
89743      * <ul>
89744      * <li>Safari 3.2 and older: cut/paste in textareas via the context menu, and dragging text into textareas</li>
89745      * <li>Opera 10 and 11: dragging text into text fields and textareas, and cut via the context menu in text
89746      * fields and textareas</li>
89747      * <li>Opera 9: Same as Opera 10 and 11, plus paste from context menu in text fields and textareas</li>
89748      * </ul>
89749      * <p>If you need to guarantee on-the-fly change notifications including these edge cases, you can call the
89750      * {@link #checkChange} method on a repeating interval, e.g. using {@link Ext.TaskManager}, or if the field is
89751      * within a {@link Ext.form.Panel}, you can use the FormPanel's {@link Ext.form.Panel#pollForChanges}
89752      * configuration to set up such a task automatically.</p>
89753      */
89754     checkChangeEvents: Ext.isIE && (!document.documentMode || document.documentMode < 9) ?
89755                         ['change', 'propertychange'] :
89756                         ['change', 'input', 'textInput', 'keyup', 'dragdrop'],
89757
89758     /**
89759      * @cfg {Number} checkChangeBuffer
89760      * Defines a timeout in milliseconds for buffering {@link #checkChangeEvents} that fire in rapid succession.
89761      * Defaults to 50 milliseconds.
89762      */
89763     checkChangeBuffer: 50,
89764
89765     componentLayout: 'field',
89766
89767     /**
89768      * @cfg {Boolean} readOnly <tt>true</tt> to mark the field as readOnly in HTML
89769      * (defaults to <tt>false</tt>).
89770      * <br><p><b>Note</b>: this only sets the element's readOnly DOM attribute.
89771      * Setting <code>readOnly=true</code>, for example, will not disable triggering a
89772      * ComboBox or Date; it gives you the option of forcing the user to choose
89773      * via the trigger without typing in the text box. To hide the trigger use
89774      * <code>{@link Ext.form.field.Trigger#hideTrigger hideTrigger}</code>.</p>
89775      */
89776     readOnly : false,
89777
89778     /**
89779      * @cfg {String} readOnlyCls The CSS class applied to the component's main element when it is {@link #readOnly}.
89780      */
89781     readOnlyCls: Ext.baseCSSPrefix + 'form-readonly',
89782
89783     /**
89784      * @cfg {String} inputId
89785      * The id that will be given to the generated input DOM element. Defaults to an automatically generated id.
89786      * If you configure this manually, you must make sure it is unique in the document.
89787      */
89788
89789     /**
89790      * @cfg {Boolean} validateOnBlur
89791      * Whether the field should validate when it loses focus (defaults to <tt>true</tt>). This will cause fields
89792      * to be validated as the user steps through the fields in the form regardless of whether they are making
89793      * changes to those fields along the way. See also {@link #validateOnChange}.
89794      */
89795     validateOnBlur: true,
89796
89797     // private
89798     hasFocus : false,
89799     
89800     baseCls: Ext.baseCSSPrefix + 'field',
89801     
89802     maskOnDisable: false,
89803
89804     // private
89805     initComponent : function() {
89806         var me = this;
89807
89808         me.callParent();
89809
89810         me.subTplData = me.subTplData || {};
89811
89812         me.addEvents(
89813             /**
89814              * @event focus
89815              * Fires when this field receives input focus.
89816              * @param {Ext.form.field.Base} this
89817              */
89818             'focus',
89819             /**
89820              * @event blur
89821              * Fires when this field loses input focus.
89822              * @param {Ext.form.field.Base} this
89823              */
89824             'blur',
89825             /**
89826              * @event specialkey
89827              * Fires when any key related to navigation (arrows, tab, enter, esc, etc.) is pressed.
89828              * To handle other keys see {@link Ext.panel.Panel#keys} or {@link Ext.util.KeyMap}.
89829              * You can check {@link Ext.EventObject#getKey} to determine which key was pressed.
89830              * For example: <pre><code>
89831 var form = new Ext.form.Panel({
89832     ...
89833     items: [{
89834             fieldLabel: 'Field 1',
89835             name: 'field1',
89836             allowBlank: false
89837         },{
89838             fieldLabel: 'Field 2',
89839             name: 'field2',
89840             listeners: {
89841                 specialkey: function(field, e){
89842                     // e.HOME, e.END, e.PAGE_UP, e.PAGE_DOWN,
89843                     // e.TAB, e.ESC, arrow keys: e.LEFT, e.RIGHT, e.UP, e.DOWN
89844                     if (e.{@link Ext.EventObject#getKey getKey()} == e.ENTER) {
89845                         var form = field.up('form').getForm();
89846                         form.submit();
89847                     }
89848                 }
89849             }
89850         }
89851     ],
89852     ...
89853 });
89854              * </code></pre>
89855              * @param {Ext.form.field.Base} this
89856              * @param {Ext.EventObject} e The event object
89857              */
89858             'specialkey'
89859         );
89860
89861         // Init mixins
89862         me.initLabelable();
89863         me.initField();
89864
89865         // Default name to inputId
89866         if (!me.name) {
89867             me.name = me.getInputId();
89868         }
89869     },
89870
89871     /**
89872      * Returns the input id for this field. If none was specified via the {@link #inputId} config,
89873      * then an id will be automatically generated.
89874      */
89875     getInputId: function() {
89876         return this.inputId || (this.inputId = Ext.id());
89877     },
89878
89879     /**
89880      * @protected Creates and returns the data object to be used when rendering the {@link #fieldSubTpl}.
89881      * @return {Object} The template data
89882      */
89883     getSubTplData: function() {
89884         var me = this,
89885             type = me.inputType,
89886             inputId = me.getInputId();
89887
89888         return Ext.applyIf(me.subTplData, {
89889             id: inputId,
89890             name: me.name || inputId,
89891             type: type,
89892             size: me.size || 20,
89893             cls: me.cls,
89894             fieldCls: me.fieldCls,
89895             tabIdx: me.tabIndex,
89896             typeCls: Ext.baseCSSPrefix + 'form-' + (type === 'password' ? 'text' : type)
89897         });
89898     },
89899
89900     /**
89901      * @protected
89902      * Gets the markup to be inserted into the outer template's bodyEl. For fields this is the
89903      * actual input element.
89904      */
89905     getSubTplMarkup: function() {
89906         return this.getTpl('fieldSubTpl').apply(this.getSubTplData());
89907     },
89908
89909     initRenderTpl: function() {
89910         var me = this;
89911         if (!me.hasOwnProperty('renderTpl')) {
89912             me.renderTpl = me.getTpl('labelableRenderTpl');
89913         }
89914         return me.callParent();
89915     },
89916
89917     initRenderData: function() {
89918         return Ext.applyIf(this.callParent(), this.getLabelableRenderData());
89919     },
89920
89921     /**
89922      * Set the {@link #fieldStyle CSS style} of the {@link #inputEl field input element}.
89923      * @param {String/Object/Function} style The style(s) to apply. Should be a valid argument to
89924      * {@link Ext.core.Element#applyStyles}.
89925      */
89926     setFieldStyle: function(style) {
89927         var me = this,
89928             inputEl = me.inputEl;
89929         if (inputEl) {
89930             inputEl.applyStyles(style);
89931         }
89932         me.fieldStyle = style;
89933     },
89934
89935     // private
89936     onRender : function() {
89937         var me = this,
89938             fieldStyle = me.fieldStyle,
89939             renderSelectors = me.renderSelectors;
89940
89941         Ext.applyIf(renderSelectors, me.getLabelableSelectors());
89942
89943         Ext.applyIf(renderSelectors, {
89944             /**
89945              * @property inputEl
89946              * @type Ext.core.Element
89947              * The input Element for this Field. Only available after the field has been rendered.
89948              */
89949             inputEl: '.' + me.fieldCls
89950         });
89951
89952         me.callParent(arguments);
89953
89954         // Make the stored rawValue get set as the input element's value
89955         me.setRawValue(me.rawValue);
89956
89957         if (me.readOnly) {
89958             me.setReadOnly(true);
89959         }
89960         if (me.disabled) {
89961             me.disable();
89962         }
89963         if (fieldStyle) {
89964             me.setFieldStyle(fieldStyle);
89965         }
89966
89967         me.renderActiveError();
89968     },
89969
89970     initAria: function() {
89971         var me = this;
89972         me.callParent();
89973
89974         // Associate the field to the error message element
89975         me.getActionEl().dom.setAttribute('aria-describedby', Ext.id(me.errorEl));
89976     },
89977
89978     getFocusEl: function() {
89979         return this.inputEl;
89980     },
89981
89982     isFileUpload: function() {
89983         return this.inputType === 'file';
89984     },
89985
89986     extractFileInput: function() {
89987         var me = this,
89988             fileInput = me.isFileUpload() ? me.inputEl.dom : null,
89989             clone;
89990         if (fileInput) {
89991             clone = fileInput.cloneNode(true);
89992             fileInput.parentNode.replaceChild(clone, fileInput);
89993             me.inputEl = Ext.get(clone);
89994         }
89995         return fileInput;
89996     },
89997
89998     // private override to use getSubmitValue() as a convenience
89999     getSubmitData: function() {
90000         var me = this,
90001             data = null,
90002             val;
90003         if (!me.disabled && me.submitValue && !me.isFileUpload()) {
90004             val = me.getSubmitValue();
90005             if (val !== null) {
90006                 data = {};
90007                 data[me.getName()] = val;
90008             }
90009         }
90010         return data;
90011     },
90012
90013     /**
90014      * <p>Returns the value that would be included in a standard form submit for this field. This will be combined
90015      * with the field's name to form a <tt>name=value</tt> pair in the {@link #getSubmitData submitted parameters}.
90016      * If an empty string is returned then just the <tt>name=</tt> will be submitted; if <tt>null</tt> is returned
90017      * then nothing will be submitted.</p>
90018      * <p>Note that the value returned will have been {@link #processRawValue processed} but may or may not have
90019      * been successfully {@link #validate validated}.</p>
90020      * @return {String} The value to be submitted, or <tt>null</tt>.
90021      */
90022     getSubmitValue: function() {
90023         return this.processRawValue(this.getRawValue());
90024     },
90025
90026     /**
90027      * Returns the raw value of the field, without performing any normalization, conversion, or validation.
90028      * To get a normalized and converted value see {@link #getValue}.
90029      * @return {String} value The raw String value of the field
90030      */
90031     getRawValue: function() {
90032         var me = this,
90033             v = (me.inputEl ? me.inputEl.getValue() : Ext.value(me.rawValue, ''));
90034         me.rawValue = v;
90035         return v;
90036     },
90037
90038     /**
90039      * Sets the field's raw value directly, bypassing {@link #valueToRaw value conversion}, change detection, and
90040      * validation. To set the value with these additional inspections see {@link #setValue}.
90041      * @param {Mixed} value The value to set
90042      * @return {Mixed} value The field value that is set
90043      */
90044     setRawValue: function(value) {
90045         var me = this;
90046         value = Ext.value(value, '');
90047         me.rawValue = value;
90048
90049         // Some Field subclasses may not render an inputEl
90050         if (me.inputEl) {
90051             me.inputEl.dom.value = value;
90052         }
90053         return value;
90054     },
90055
90056     /**
90057      * <p>Converts a mixed-type value to a raw representation suitable for displaying in the field. This allows
90058      * controlling how value objects passed to {@link #setValue} are shown to the user, including localization.
90059      * For instance, for a {@link Ext.form.field.Date}, this would control how a Date object passed to {@link #setValue}
90060      * would be converted to a String for display in the field.</p>
90061      * <p>See {@link #rawToValue} for the opposite conversion.</p>
90062      * <p>The base implementation simply does a standard toString conversion, and converts
90063      * {@link Ext#isEmpty empty values} to an empty string.</p>
90064      * @param {Mixed} value The mixed-type value to convert to the raw representation.
90065      * @return {Mixed} The converted raw value.
90066      */
90067     valueToRaw: function(value) {
90068         return '' + Ext.value(value, '');
90069     },
90070
90071     /**
90072      * <p>Converts a raw input field value into a mixed-type value that is suitable for this particular field type.
90073      * This allows controlling the normalization and conversion of user-entered values into field-type-appropriate
90074      * values, e.g. a Date object for {@link Ext.form.field.Date}, and is invoked by {@link #getValue}.</p>
90075      * <p>It is up to individual implementations to decide how to handle raw values that cannot be successfully
90076      * converted to the desired object type.</p>
90077      * <p>See {@link #valueToRaw} for the opposite conversion.</p>
90078      * <p>The base implementation does no conversion, returning the raw value untouched.</p>
90079      * @param {Mixed} rawValue
90080      * @return {Mixed} The converted value.
90081      */
90082     rawToValue: function(rawValue) {
90083         return rawValue;
90084     },
90085
90086     /**
90087      * Performs any necessary manipulation of a raw field value to prepare it for {@link #rawToValue conversion}
90088      * and/or {@link #validate validation}, for instance stripping out ignored characters. In the base implementation
90089      * it does nothing; individual subclasses may override this as needed.
90090      * @param {Mixed} value The unprocessed string value
90091      * @return {Mixed} The processed string value
90092      */
90093     processRawValue: function(value) {
90094         return value;
90095     },
90096
90097     /**
90098      * Returns the current data value of the field. The type of value returned is particular to the type of the
90099      * particular field (e.g. a Date object for {@link Ext.form.field.Date}), as the result of calling {@link #rawToValue} on
90100      * the field's {@link #processRawValue processed} String value. To return the raw String value, see {@link #getRawValue}.
90101      * @return {Mixed} value The field value
90102      */
90103     getValue: function() {
90104         var me = this,
90105             val = me.rawToValue(me.processRawValue(me.getRawValue()));
90106         me.value = val;
90107         return val;
90108     },
90109
90110     /**
90111      * Sets a data value into the field and runs the change detection and validation. To set the value directly
90112      * without these inspections see {@link #setRawValue}.
90113      * @param {Mixed} value The value to set
90114      * @return {Ext.form.field.Field} this
90115      */
90116     setValue: function(value) {
90117         var me = this;
90118         me.setRawValue(me.valueToRaw(value));
90119         return me.mixins.field.setValue.call(me, value);
90120     },
90121
90122
90123     //private
90124     onDisable: function() {
90125         var me = this,
90126             inputEl = me.inputEl;
90127         me.callParent();
90128         if (inputEl) {
90129             inputEl.dom.disabled = true;
90130         }
90131     },
90132
90133     //private
90134     onEnable: function() {
90135         var me = this,
90136             inputEl = me.inputEl;
90137         me.callParent();
90138         if (inputEl) {
90139             inputEl.dom.disabled = false;
90140         }
90141     },
90142
90143     /**
90144      * Sets the read only state of this field.
90145      * @param {Boolean} readOnly Whether the field should be read only.
90146      */
90147     setReadOnly: function(readOnly) {
90148         var me = this,
90149             inputEl = me.inputEl;
90150         if (inputEl) {
90151             inputEl.dom.readOnly = readOnly;
90152             inputEl.dom.setAttribute('aria-readonly', readOnly);
90153         }
90154         me[readOnly ? 'addCls' : 'removeCls'](me.readOnlyCls);
90155         me.readOnly = readOnly;
90156     },
90157
90158     // private
90159     fireKey: function(e){
90160         if(e.isSpecialKey()){
90161             this.fireEvent('specialkey', this, Ext.create('Ext.EventObjectImpl', e));
90162         }
90163     },
90164
90165     // private
90166     initEvents : function(){
90167         var me = this,
90168             inputEl = me.inputEl,
90169             onChangeTask,
90170             onChangeEvent;
90171         if (inputEl) {
90172             me.mon(inputEl, Ext.EventManager.getKeyEvent(), me.fireKey,  me);
90173             me.mon(inputEl, 'focus', me.onFocus, me);
90174
90175             // standardise buffer across all browsers + OS-es for consistent event order.
90176             // (the 10ms buffer for Editors fixes a weird FF/Win editor issue when changing OS window focus)
90177             me.mon(inputEl, 'blur', me.onBlur, me, me.inEditor ? {buffer:10} : null);
90178
90179             // listen for immediate value changes
90180             onChangeTask = Ext.create('Ext.util.DelayedTask', me.checkChange, me);
90181             me.onChangeEvent = onChangeEvent = function() {
90182                 onChangeTask.delay(me.checkChangeBuffer);
90183             };
90184             Ext.each(me.checkChangeEvents, function(eventName) {
90185                 if (eventName === 'propertychange') {
90186                     me.usesPropertychange = true;
90187                 }
90188                 me.mon(inputEl, eventName, onChangeEvent);
90189             }, me);
90190         }
90191         me.callParent();
90192     },
90193
90194     doComponentLayout: function() {
90195         var me = this,
90196             inputEl = me.inputEl,
90197             usesPropertychange = me.usesPropertychange,
90198             ename = 'propertychange',
90199             onChangeEvent = me.onChangeEvent;
90200
90201         // In IE if propertychange is one of the checkChangeEvents, we need to remove
90202         // the listener prior to layout and re-add it after, to prevent it from firing
90203         // needlessly for attribute and style changes applied to the inputEl.
90204         if (usesPropertychange) {
90205             me.mun(inputEl, ename, onChangeEvent);
90206         }
90207         me.callParent(arguments);
90208         if (usesPropertychange) {
90209             me.mon(inputEl, ename, onChangeEvent);
90210         }
90211     },
90212
90213     // private
90214     preFocus: Ext.emptyFn,
90215
90216     // private
90217     onFocus: function() {
90218         var me = this,
90219             focusCls = me.focusCls,
90220             inputEl = me.inputEl;
90221         me.preFocus();
90222         if (focusCls && inputEl) {
90223             inputEl.addCls(focusCls);
90224         }
90225         if (!me.hasFocus) {
90226             me.hasFocus = true;
90227             me.fireEvent('focus', me);
90228         }
90229     },
90230
90231     // private
90232     beforeBlur : Ext.emptyFn,
90233
90234     // private
90235     onBlur : function(){
90236         var me = this,
90237             focusCls = me.focusCls,
90238             inputEl = me.inputEl;
90239         me.beforeBlur();
90240         if (focusCls && inputEl) {
90241             inputEl.removeCls(focusCls);
90242         }
90243         if (me.validateOnBlur) {
90244             me.validate();
90245         }
90246         me.hasFocus = false;
90247         me.fireEvent('blur', me);
90248         me.postBlur();
90249     },
90250
90251     // private
90252     postBlur : Ext.emptyFn,
90253
90254
90255     /**
90256      * @private Called when the field's dirty state changes. Adds/removes the {@link #dirtyCls} on the main element.
90257      * @param {Boolean} isDirty
90258      */
90259     onDirtyChange: function(isDirty) {
90260         this[isDirty ? 'addCls' : 'removeCls'](this.dirtyCls);
90261     },
90262
90263
90264     /**
90265      * Returns whether or not the field value is currently valid by
90266      * {@link #getErrors validating} the {@link #processRawValue processed raw value}
90267      * of the field. <b>Note</b>: {@link #disabled} fields are always treated as valid.
90268      * @return {Boolean} True if the value is valid, else false
90269      */
90270     isValid : function() {
90271         var me = this;
90272         return me.disabled || me.validateValue(me.processRawValue(me.getRawValue()));
90273     },
90274
90275
90276     /**
90277      * <p>Uses {@link #getErrors} to build an array of validation errors. If any errors are found, they are passed
90278      * to {@link #markInvalid} and false is returned, otherwise true is returned.</p>
90279      * <p>Previously, subclasses were invited to provide an implementation of this to process validations - from 3.2
90280      * onwards {@link #getErrors} should be overridden instead.</p>
90281      * @param {Mixed} value The value to validate
90282      * @return {Boolean} True if all validations passed, false if one or more failed
90283      */
90284     validateValue: function(value) {
90285         var me = this,
90286             errors = me.getErrors(value),
90287             isValid = Ext.isEmpty(errors);
90288         if (!me.preventMark) {
90289             if (isValid) {
90290                 me.clearInvalid();
90291             } else {
90292                 me.markInvalid(errors);
90293             }
90294         }
90295
90296         return isValid;
90297     },
90298
90299     /**
90300      * <p>Display one or more error messages associated with this field, using {@link #msgTarget} to determine how to
90301      * display the messages and applying {@link #invalidCls} to the field's UI element.</p>
90302      * <p><b>Note</b>: this method does not cause the Field's {@link #validate} or {@link #isValid} methods to
90303      * return <code>false</code> if the value does <i>pass</i> validation. So simply marking a Field as invalid
90304      * will not prevent submission of forms submitted with the {@link Ext.form.action.Submit#clientValidation}
90305      * option set.</p>
90306      * @param {String/Array} errors The validation message(s) to display.
90307      */
90308     markInvalid : function(errors) {
90309         // Save the message and fire the 'invalid' event
90310         var me = this,
90311             oldMsg = me.getActiveError();
90312         me.setActiveErrors(Ext.Array.from(errors));
90313         if (oldMsg !== me.getActiveError()) {
90314             me.doComponentLayout();
90315         }
90316     },
90317
90318     /**
90319      * <p>Clear any invalid styles/messages for this field.</p>
90320      * <p><b>Note</b>: this method does not cause the Field's {@link #validate} or {@link #isValid} methods to
90321      * return <code>true</code> if the value does not <i>pass</i> validation. So simply clearing a field's errors
90322      * will not necessarily allow submission of forms submitted with the {@link Ext.form.action.Submit#clientValidation}
90323      * option set.</p>
90324      */
90325     clearInvalid : function() {
90326         // Clear the message and fire the 'valid' event
90327         var me = this,
90328             hadError = me.hasActiveError();
90329         me.unsetActiveError();
90330         if (hadError) {
90331             me.doComponentLayout();
90332         }
90333     },
90334
90335     /**
90336      * @private Overrides the method from the Ext.form.Labelable mixin to also add the invalidCls to the inputEl,
90337      * as that is required for proper styling in IE with nested fields (due to lack of child selector)
90338      */
90339     renderActiveError: function() {
90340         var me = this,
90341             hasError = me.hasActiveError();
90342         if (me.inputEl) {
90343             // Add/remove invalid class
90344             me.inputEl[hasError ? 'addCls' : 'removeCls'](me.invalidCls + '-field');
90345         }
90346         me.mixins.labelable.renderActiveError.call(me);
90347     },
90348
90349
90350     getActionEl: function() {
90351         return this.inputEl || this.el;
90352     }
90353
90354 });
90355
90356 /**
90357  * @class Ext.form.field.Text
90358  * @extends Ext.form.field.Base
90359  
90360 A basic text field.  Can be used as a direct replacement for traditional text inputs,
90361 or as the base class for more sophisticated input controls (like {@link Ext.form.field.TextArea}
90362 and {@link Ext.form.field.ComboBox}). Has support for empty-field placeholder values (see {@link #emptyText}).
90363
90364 #Validation#
90365
90366 The Text field has a useful set of validations built in:
90367
90368 - {@link #allowBlank} for making the field required
90369 - {@link #minLength} for requiring a minimum value length
90370 - {@link #maxLength} for setting a maximum value length (with {@link #enforceMaxLength} to add it
90371   as the `maxlength` attribute on the input element)
90372 - {@link regex} to specify a custom regular expression for validation
90373
90374 In addition, custom validations may be added:
90375  
90376 - {@link #vtype} specifies a virtual type implementation from {@link Ext.form.field.VTypes} which can contain
90377   custom validation logic
90378 - {@link #validator} allows a custom arbitrary function to be called during validation
90379
90380 The details around how and when each of these validation options get used are described in the
90381 documentation for {@link #getErrors}.
90382
90383 By default, the field value is checked for validity immediately while the user is typing in the
90384 field. This can be controlled with the {@link #validateOnChange}, {@link #checkChangeEvents}, and
90385 {@link #checkChangeBugger} configurations. Also see the details on Form Validation in the
90386 {@link Ext.form.Panel} class documentation.
90387
90388 #Masking and Character Stripping#
90389
90390 Text fields can be configured with custom regular expressions to be applied to entered values before
90391 validation: see {@link #maskRe} and {@link #stripCharsRe} for details.
90392 {@img Ext.form.Text/Ext.form.Text.png Ext.form.Text component}
90393 #Example usage:#
90394
90395     Ext.create('Ext.form.Panel', {
90396         title: 'Contact Info',
90397         width: 300,
90398         bodyPadding: 10,
90399         renderTo: Ext.getBody(),        
90400         items: [{
90401             xtype: 'textfield',
90402             name: 'name',
90403             fieldLabel: 'Name',
90404             allowBlank: false  // requires a non-empty value
90405         }, {
90406             xtype: 'textfield',
90407             name: 'email',
90408             fieldLabel: 'Email Address',
90409             vtype: 'email'  // requires value to be a valid email address format
90410         }]
90411     }); 
90412
90413  * @constructor Creates a new TextField
90414  * @param {Object} config Configuration options
90415  *
90416  * @xtype textfield
90417  * @markdown
90418  * @docauthor Jason Johnston <jason@sencha.com>
90419  */
90420 Ext.define('Ext.form.field.Text', {
90421     extend:'Ext.form.field.Base',
90422     alias: 'widget.textfield',
90423     requires: ['Ext.form.field.VTypes', 'Ext.layout.component.field.Text'],
90424     alternateClassName: ['Ext.form.TextField', 'Ext.form.Text'],
90425
90426     /**
90427      * @cfg {String} vtypeText A custom error message to display in place of the default message provided
90428      * for the <b><code>{@link #vtype}</code></b> currently set for this field (defaults to <tt>undefined</tt>).
90429      * <b>Note</b>: only applies if <b><code>{@link #vtype}</code></b> is set, else ignored.
90430      */
90431     
90432     /**
90433      * @cfg {RegExp} stripCharsRe A JavaScript RegExp object used to strip unwanted content from the value
90434      * before validation (defaults to <tt>undefined</tt>).
90435      */
90436
90437     /**
90438      * @cfg {Number} size An initial value for the 'size' attribute on the text input element. This is only
90439      * used if the field has no configured {@link #width} and is not given a width by its container's layout.
90440      * Defaults to <tt>20</tt>.
90441      */
90442     size: 20,
90443
90444     /**
90445      * @cfg {Boolean} grow <tt>true</tt> if this field should automatically grow and shrink to its content
90446      * (defaults to <tt>false</tt>)
90447      */
90448
90449     /**
90450      * @cfg {Number} growMin The minimum width to allow when <code><b>{@link #grow}</b> = true</code> (defaults
90451      * to <tt>30</tt>)
90452      */
90453     growMin : 30,
90454     
90455     /**
90456      * @cfg {Number} growMax The maximum width to allow when <code><b>{@link #grow}</b> = true</code> (defaults
90457      * to <tt>800</tt>)
90458      */
90459     growMax : 800,
90460
90461     /**
90462      * @cfg {String} growAppend
90463      * A string that will be appended to the field's current value for the purposes of calculating the target
90464      * field size. Only used when the {@link #grow} config is <tt>true</tt>. Defaults to a single capital "W"
90465      * (the widest character in common fonts) to leave enough space for the next typed character and avoid the
90466      * field value shifting before the width is adjusted.
90467      */
90468     growAppend: 'W',
90469     
90470     /**
90471      * @cfg {String} vtype A validation type name as defined in {@link Ext.form.field.VTypes} (defaults to <tt>undefined</tt>)
90472      */
90473
90474     /**
90475      * @cfg {RegExp} maskRe An input mask regular expression that will be used to filter keystrokes that do
90476      * not match (defaults to <tt>undefined</tt>)
90477      */
90478
90479     /**
90480      * @cfg {Boolean} disableKeyFilter Specify <tt>true</tt> to disable input keystroke filtering (defaults
90481      * to <tt>false</tt>)
90482      */
90483
90484     /**
90485      * @cfg {Boolean} allowBlank Specify <tt>false</tt> to validate that the value's length is > 0 (defaults to
90486      * <tt>true</tt>)
90487      */
90488     allowBlank : true,
90489     
90490     /**
90491      * @cfg {Number} minLength Minimum input field length required (defaults to <tt>0</tt>)
90492      */
90493     minLength : 0,
90494     
90495     /**
90496      * @cfg {Number} maxLength Maximum input field length allowed by validation (defaults to Number.MAX_VALUE).
90497      * This behavior is intended to provide instant feedback to the user by improving usability to allow pasting
90498      * and editing or overtyping and back tracking. To restrict the maximum number of characters that can be
90499      * entered into the field use the <tt><b>{@link Ext.form.field.Text#enforceMaxLength enforceMaxLength}</b></tt> option.
90500      */
90501     maxLength : Number.MAX_VALUE,
90502     
90503     /**
90504      * @cfg {Boolean} enforceMaxLength True to set the maxLength property on the underlying input field. Defaults to <tt>false</tt>
90505      */
90506
90507     /**
90508      * @cfg {String} minLengthText Error text to display if the <b><tt>{@link #minLength minimum length}</tt></b>
90509      * validation fails (defaults to <tt>'The minimum length for this field is {minLength}'</tt>)
90510      */
90511     minLengthText : 'The minimum length for this field is {0}',
90512     
90513     /**
90514      * @cfg {String} maxLengthText Error text to display if the <b><tt>{@link #maxLength maximum length}</tt></b>
90515      * validation fails (defaults to <tt>'The maximum length for this field is {maxLength}'</tt>)
90516      */
90517     maxLengthText : 'The maximum length for this field is {0}',
90518     
90519     /**
90520      * @cfg {Boolean} selectOnFocus <tt>true</tt> to automatically select any existing field text when the field
90521      * receives input focus (defaults to <tt>false</tt>)
90522      */
90523     
90524     /**
90525      * @cfg {String} blankText The error text to display if the <b><tt>{@link #allowBlank}</tt></b> validation
90526      * fails (defaults to <tt>'This field is required'</tt>)
90527      */
90528     blankText : 'This field is required',
90529     
90530     /**
90531      * @cfg {Function} validator
90532      * <p>A custom validation function to be called during field validation ({@link #getErrors})
90533      * (defaults to <tt>undefined</tt>). If specified, this function will be called first, allowing the
90534      * developer to override the default validation process.</p>
90535      * <br><p>This function will be passed the following Parameters:</p>
90536      * <div class="mdetail-params"><ul>
90537      * <li><code>value</code>: <i>Mixed</i>
90538      * <div class="sub-desc">The current field value</div></li>
90539      * </ul></div>
90540      * <br><p>This function is to Return:</p>
90541      * <div class="mdetail-params"><ul>
90542      * <li><code>true</code>: <i>Boolean</i>
90543      * <div class="sub-desc"><code>true</code> if the value is valid</div></li>
90544      * <li><code>msg</code>: <i>String</i>
90545      * <div class="sub-desc">An error message if the value is invalid</div></li>
90546      * </ul></div>
90547      */
90548
90549     /**
90550      * @cfg {RegExp} regex A JavaScript RegExp object to be tested against the field value during validation
90551      * (defaults to <tt>undefined</tt>). If the test fails, the field will be marked invalid using
90552      * <b><tt>{@link #regexText}</tt></b>.
90553      */
90554
90555     /**
90556      * @cfg {String} regexText The error text to display if <b><tt>{@link #regex}</tt></b> is used and the
90557      * test fails during validation (defaults to <tt>''</tt>)
90558      */
90559     regexText : '',
90560     
90561     /**
90562      * @cfg {String} emptyText
90563      * <p>The default text to place into an empty field (defaults to <tt>undefined</tt>).</p>
90564      * <p>Note that normally this value will be submitted to the server if this field is enabled; to prevent this
90565      * you can set the {@link Ext.form.action.Action#submitEmptyText submitEmptyText} option of
90566      * {@link Ext.form.Basic#submit} to <tt>false</tt>.</p>
90567      * <p>Also note that if you use <tt>{@link #inputType inputType}:'file'</tt>, {@link #emptyText} is not
90568      * supported and should be avoided.</p>
90569      */
90570
90571     /**
90572      * @cfg {String} emptyCls The CSS class to apply to an empty field to style the <b><tt>{@link #emptyText}</tt></b>
90573      * (defaults to <tt>'x-form-empty-field'</tt>).  This class is automatically added and removed as needed
90574      * depending on the current field value.
90575      */
90576     emptyCls : Ext.baseCSSPrefix + 'form-empty-field',
90577
90578     ariaRole: 'textbox',
90579
90580     /**
90581      * @cfg {Boolean} enableKeyEvents <tt>true</tt> to enable the proxying of key events for the HTML input field (defaults to <tt>false</tt>)
90582      */
90583
90584     componentLayout: 'textfield',
90585
90586     initComponent : function(){
90587         this.callParent();
90588         this.addEvents(
90589             /**
90590              * @event autosize
90591              * Fires when the <tt><b>{@link #autoSize}</b></tt> function is triggered and the field is
90592              * resized according to the {@link #grow}/{@link #growMin}/{@link #growMax} configs as a result.
90593              * This event provides a hook for the developer to apply additional logic at runtime to resize the
90594              * field if needed.
90595              * @param {Ext.form.field.Text} this This text field
90596              * @param {Number} width The new field width
90597              */
90598             'autosize',
90599
90600             /**
90601              * @event keydown
90602              * Keydown input field event. This event only fires if <tt><b>{@link #enableKeyEvents}</b></tt>
90603              * is set to true.
90604              * @param {Ext.form.field.Text} this This text field
90605              * @param {Ext.EventObject} e
90606              */
90607             'keydown',
90608             /**
90609              * @event keyup
90610              * Keyup input field event. This event only fires if <tt><b>{@link #enableKeyEvents}</b></tt>
90611              * is set to true.
90612              * @param {Ext.form.field.Text} this This text field
90613              * @param {Ext.EventObject} e
90614              */
90615             'keyup',
90616             /**
90617              * @event keypress
90618              * Keypress input field event. This event only fires if <tt><b>{@link #enableKeyEvents}</b></tt>
90619              * is set to true.
90620              * @param {Ext.form.field.Text} this This text field
90621              * @param {Ext.EventObject} e
90622              */
90623             'keypress'
90624         );
90625     },
90626
90627     // private
90628     initEvents : function(){
90629         var me = this,
90630             el = me.inputEl;
90631         
90632         me.callParent();
90633         if(me.selectOnFocus || me.emptyText){
90634             me.mon(el, 'mousedown', me.onMouseDown, me);
90635         }
90636         if(me.maskRe || (me.vtype && me.disableKeyFilter !== true && (me.maskRe = Ext.form.field.VTypes[me.vtype+'Mask']))){
90637             me.mon(el, 'keypress', me.filterKeys, me);
90638         }
90639
90640         if (me.enableKeyEvents) {
90641             me.mon(el, {
90642                 scope: me,
90643                 keyup: me.onKeyUp,
90644                 keydown: me.onKeyDown,
90645                 keypress: me.onKeyPress
90646             });
90647         }
90648     },
90649
90650     /**
90651      * @private override - treat undefined and null values as equal to an empty string value
90652      */
90653     isEqual: function(value1, value2) {
90654         return String(Ext.value(value1, '')) === String(Ext.value(value2, ''));
90655     },
90656
90657     /**
90658      * @private
90659      * If grow=true, invoke the autoSize method when the field's value is changed.
90660      */
90661     onChange: function() {
90662         this.callParent();
90663         this.autoSize();
90664     },
90665     
90666     afterRender: function(){
90667         var me = this;
90668         if (me.enforceMaxLength) {
90669             me.inputEl.dom.maxLength = me.maxLength;
90670         }
90671         me.applyEmptyText();
90672         me.autoSize();
90673         me.callParent();
90674     },
90675
90676     onMouseDown: function(e){
90677         var me = this;
90678         if(!me.hasFocus){
90679             me.mon(me.inputEl, 'mouseup', Ext.emptyFn, me, { single: true, preventDefault: true });
90680         }
90681     },
90682
90683     /**
90684      * Performs any necessary manipulation of a raw String value to prepare it for {@link #stringToValue conversion}
90685      * and/or {@link #validate validation}. For text fields this applies the configured {@link #stripCharsRe} to the
90686      * raw value.
90687      * @param {String} value The unprocessed string value
90688      * @return {String} The processed string value
90689      */
90690     processRawValue: function(value) {
90691         var me = this,
90692             stripRe = me.stripCharsRe,
90693             newValue;
90694             
90695         if (stripRe) {
90696             newValue = value.replace(stripRe, '');
90697             if (newValue !== value) {
90698                 me.setRawValue(newValue);
90699                 value = newValue;
90700             }
90701         }
90702         return value;
90703     },
90704
90705     //private
90706     onDisable: function(){
90707         this.callParent();
90708         if (Ext.isIE) {
90709             this.inputEl.dom.unselectable = 'on';
90710         }
90711     },
90712
90713     //private
90714     onEnable: function(){
90715         this.callParent();
90716         if (Ext.isIE) {
90717             this.inputEl.dom.unselectable = '';
90718         }
90719     },
90720
90721     onKeyDown: function(e) {
90722         this.fireEvent('keydown', this, e);
90723     },
90724
90725     onKeyUp: function(e) {
90726         this.fireEvent('keyup', this, e);
90727     },
90728
90729     onKeyPress: function(e) {
90730         this.fireEvent('keypress', this, e);
90731     },
90732
90733     /**
90734      * Resets the current field value to the originally-loaded value and clears any validation messages.
90735      * Also adds <tt><b>{@link #emptyText}</b></tt> and <tt><b>{@link #emptyCls}</b></tt> if the
90736      * original value was blank.
90737      */
90738     reset : function(){
90739         this.callParent();
90740         this.applyEmptyText();
90741     },
90742
90743     applyEmptyText : function(){
90744         var me = this,
90745             emptyText = me.emptyText,
90746             isEmpty;
90747
90748         if (me.rendered && emptyText) {
90749             isEmpty = me.getRawValue().length < 1 && !me.hasFocus;
90750             
90751             if (Ext.supports.Placeholder) {
90752                 me.inputEl.dom.placeholder = emptyText;
90753             } else if (isEmpty) {
90754                 me.setRawValue(emptyText);
90755             }
90756             
90757             //all browsers need this because of a styling issue with chrome + placeholders.
90758             //the text isnt vertically aligned when empty (and using the placeholder)
90759             if (isEmpty) {
90760                 me.inputEl.addCls(me.emptyCls);
90761             }
90762
90763             me.autoSize();
90764         }
90765     },
90766
90767     // private
90768     preFocus : function(){
90769         var me = this,
90770             inputEl = me.inputEl,
90771             emptyText = me.emptyText,
90772             isEmpty;
90773
90774         if (emptyText && !Ext.supports.Placeholder && inputEl.dom.value === emptyText) {
90775             me.setRawValue('');
90776             isEmpty = true;
90777             inputEl.removeCls(me.emptyCls);
90778         } else if (Ext.supports.Placeholder) {
90779             me.inputEl.removeCls(me.emptyCls);
90780         }
90781         if (me.selectOnFocus || isEmpty) {
90782             inputEl.dom.select();
90783         }
90784     },
90785
90786     onFocus: function() {
90787         var me = this;
90788         me.callParent(arguments);
90789         if (me.emptyText) {
90790             me.autoSize();
90791         }
90792     },
90793
90794     // private
90795     postBlur : function(){
90796         this.applyEmptyText();
90797     },
90798
90799     // private
90800     filterKeys : function(e){
90801         if(e.ctrlKey){
90802             return;
90803         }
90804         var key = e.getKey(),
90805             charCode = String.fromCharCode(e.getCharCode());
90806             
90807         if(Ext.isGecko && (e.isNavKeyPress() || key === e.BACKSPACE || (key === e.DELETE && e.button === -1))){
90808             return;
90809         }
90810         
90811         if(!Ext.isGecko && e.isSpecialKey() && !charCode){
90812             return;
90813         }
90814         if(!this.maskRe.test(charCode)){
90815             e.stopEvent();
90816         }
90817     },
90818
90819     /**
90820      * Returns the raw String value of the field, without performing any normalization, conversion, or validation.
90821      * Gets the current value of the input element if the field has been rendered, ignoring the value if it is the
90822      * {@link #emptyText}. To get a normalized and converted value see {@link #getValue}.
90823      * @return {String} value The raw String value of the field
90824      */
90825     getRawValue: function() {
90826         var me = this,
90827             v = me.callParent();
90828         if (v === me.emptyText) {
90829             v = '';
90830         }
90831         return v;
90832     },
90833
90834     /**
90835      * Sets a data value into the field and runs the change detection and validation. Also applies any configured
90836      * {@link #emptyText} for text fields. To set the value directly without these inspections see {@link #setRawValue}.
90837      * @param {Mixed} value The value to set
90838      * @return {Ext.form.field.Text} this
90839      */
90840     setValue: function(value) {
90841         var me = this,
90842             inputEl = me.inputEl;
90843         
90844         if (inputEl && me.emptyText && !Ext.isEmpty(value)) {
90845             inputEl.removeCls(me.emptyCls);
90846         }
90847         
90848         me.callParent(arguments);
90849
90850         me.applyEmptyText();
90851         return me;
90852     },
90853
90854     /**
90855 Validates a value according to the field's validation rules and returns an array of errors
90856 for any failing validations. Validation rules are processed in the following order:
90857
90858 1. **Field specific validator**
90859     
90860     A validator offers a way to customize and reuse a validation specification.
90861     If a field is configured with a `{@link #validator}`
90862     function, it will be passed the current field value.  The `{@link #validator}`
90863     function is expected to return either:
90864     
90865     - Boolean `true`  if the value is valid (validation continues).
90866     - a String to represent the invalid message if invalid (validation halts).
90867
90868 2. **Basic Validation**
90869
90870     If the `{@link #validator}` has not halted validation,
90871     basic validation proceeds as follows:
90872     
90873     - `{@link #allowBlank}` : (Invalid message = `{@link #emptyText}`)
90874     
90875         Depending on the configuration of <code>{@link #allowBlank}</code>, a
90876         blank field will cause validation to halt at this step and return
90877         Boolean true or false accordingly.
90878     
90879     - `{@link #minLength}` : (Invalid message = `{@link #minLengthText}`)
90880
90881         If the passed value does not satisfy the `{@link #minLength}`
90882         specified, validation halts.
90883
90884     -  `{@link #maxLength}` : (Invalid message = `{@link #maxLengthText}`)
90885
90886         If the passed value does not satisfy the `{@link #maxLength}`
90887         specified, validation halts.
90888
90889 3. **Preconfigured Validation Types (VTypes)**
90890
90891     If none of the prior validation steps halts validation, a field
90892     configured with a `{@link #vtype}` will utilize the
90893     corresponding {@link Ext.form.field.VTypes VTypes} validation function.
90894     If invalid, either the field's `{@link #vtypeText}` or
90895     the VTypes vtype Text property will be used for the invalid message.
90896     Keystrokes on the field will be filtered according to the VTypes
90897     vtype Mask property.
90898
90899 4. **Field specific regex test**
90900
90901     If none of the prior validation steps halts validation, a field's
90902     configured <code>{@link #regex}</code> test will be processed.
90903     The invalid message for this test is configured with `{@link #regexText}`
90904
90905      * @param {Mixed} value The value to validate. The processed raw value will be used if nothing is passed
90906      * @return {Array} Array of any validation errors
90907      * @markdown
90908      */
90909     getErrors: function(value) {
90910         var me = this,
90911             errors = me.callParent(arguments),
90912             validator = me.validator,
90913             emptyText = me.emptyText,
90914             allowBlank = me.allowBlank,
90915             vtype = me.vtype,
90916             vtypes = Ext.form.field.VTypes,
90917             regex = me.regex,
90918             format = Ext.String.format,
90919             msg;
90920
90921         value = value || me.processRawValue(me.getRawValue());
90922
90923         if (Ext.isFunction(validator)) {
90924             msg = validator.call(me, value);
90925             if (msg !== true) {
90926                 errors.push(msg);
90927             }
90928         }
90929
90930         if (value.length < 1 || value === emptyText) {
90931             if (!allowBlank) {
90932                 errors.push(me.blankText);
90933             }
90934             //if value is blank, there cannot be any additional errors
90935             return errors;
90936         }
90937
90938         if (value.length < me.minLength) {
90939             errors.push(format(me.minLengthText, me.minLength));
90940         }
90941
90942         if (value.length > me.maxLength) {
90943             errors.push(format(me.maxLengthText, me.maxLength));
90944         }
90945
90946         if (vtype) {
90947             if(!vtypes[vtype](value, me)){
90948                 errors.push(me.vtypeText || vtypes[vtype +'Text']);
90949             }
90950         }
90951
90952         if (regex && !regex.test(value)) {
90953             errors.push(me.regexText || me.invalidText);
90954         }
90955
90956         return errors;
90957     },
90958
90959     /**
90960      * Selects text in this field
90961      * @param {Number} start (optional) The index where the selection should start (defaults to 0)
90962      * @param {Number} end (optional) The index where the selection should end (defaults to the text length)
90963      */
90964     selectText : function(start, end){
90965         var me = this,
90966             v = me.getRawValue(),
90967             doFocus = true,
90968             el = me.inputEl.dom,
90969             undef,
90970             range;
90971             
90972         if (v.length > 0) {
90973             start = start === undef ? 0 : start;
90974             end = end === undef ? v.length : end;
90975             if (el.setSelectionRange) {
90976                 el.setSelectionRange(start, end);
90977             }
90978             else if(el.createTextRange) {
90979                 range = el.createTextRange();
90980                 range.moveStart('character', start);
90981                 range.moveEnd('character', end - v.length);
90982                 range.select();
90983             }
90984             doFocus = Ext.isGecko || Ext.isOpera;
90985         }
90986         if (doFocus) {
90987             me.focus();
90988         }
90989     },
90990
90991     /**
90992      * Automatically grows the field to accomodate the width of the text up to the maximum field width allowed.
90993      * This only takes effect if <tt>{@link #grow} = true</tt>, and fires the {@link #autosize} event if the
90994      * width changes.
90995      */
90996     autoSize: function() {
90997         var me = this,
90998             width;
90999         if (me.grow && me.rendered) {
91000             me.doComponentLayout();
91001             width = me.inputEl.getWidth();
91002             if (width !== me.lastInputWidth) {
91003                 me.fireEvent('autosize', width);
91004                 me.lastInputWidth = width;
91005             }
91006         }
91007     },
91008
91009     initAria: function() {
91010         this.callParent();
91011         this.getActionEl().dom.setAttribute('aria-required', this.allowBlank === false);
91012     },
91013
91014     /**
91015      * @protected override
91016      * To get the natural width of the inputEl, we do a simple calculation based on the
91017      * 'size' config. We use hard-coded numbers to approximate what browsers do natively,
91018      * to avoid having to read any styles which would hurt performance.
91019      */
91020     getBodyNaturalWidth: function() {
91021         return Math.round(this.size * 6.5) + 20;
91022     }
91023
91024 });
91025
91026 /**
91027  * @class Ext.form.field.TextArea
91028  * @extends Ext.form.field.Text
91029
91030 This class creates a multiline text field, which can be used as a direct replacement for traditional 
91031 textarea fields. In addition, it supports automatically {@link #grow growing} the height of the textarea to 
91032 fit its content.
91033
91034 All of the configuration options from {@link Ext.form.field.Text} can be used on TextArea.
91035 {@img Ext.form.TextArea/Ext.form.TextArea.png Ext.form.TextArea component}
91036 Example usage:
91037
91038     Ext.create('Ext.form.FormPanel', {
91039         title      : 'Sample TextArea',
91040         width      : 400,
91041         bodyPadding: 10,
91042         renderTo   : Ext.getBody(),
91043         items: [{
91044             xtype     : 'textareafield',
91045             grow      : true,
91046             name      : 'message',
91047             fieldLabel: 'Message',
91048             anchor    : '100%'
91049         }]
91050     }); 
91051
91052 Some other useful configuration options when using {@link #grow} are {@link #growMin} and {@link #growMax}. These 
91053 allow you to set the minimum and maximum grow heights for the textarea.
91054
91055  * @constructor
91056  * Creates a new TextArea
91057  * @param {Object} config Configuration options
91058  * @xtype textareafield
91059  * @docauthor Robert Dougan <rob@sencha.com>
91060  */
91061 Ext.define('Ext.form.field.TextArea', {
91062     extend:'Ext.form.field.Text',
91063     alias: ['widget.textareafield', 'widget.textarea'],
91064     alternateClassName: 'Ext.form.TextArea',
91065     requires: ['Ext.XTemplate', 'Ext.layout.component.field.TextArea'],
91066
91067     fieldSubTpl: [
91068         '<textarea id="{id}" ',
91069             '<tpl if="name">name="{name}" </tpl>',
91070             '<tpl if="rows">rows="{rows}" </tpl>',
91071             '<tpl if="cols">cols="{cols}" </tpl>',
91072             '<tpl if="tabIdx">tabIndex="{tabIdx}" </tpl>',
91073             'class="{fieldCls} {typeCls}" ',
91074             'autocomplete="off">',
91075         '</textarea>',
91076         {
91077             compiled: true,
91078             disableFormats: true
91079         }
91080     ],
91081
91082     /**
91083      * @cfg {Number} growMin The minimum height to allow when <tt>{@link Ext.form.field.Text#grow grow}=true</tt>
91084      * (defaults to <tt>60</tt>)
91085      */
91086     growMin: 60,
91087
91088     /**
91089      * @cfg {Number} growMax The maximum height to allow when <tt>{@link Ext.form.field.Text#grow grow}=true</tt>
91090      * (defaults to <tt>1000</tt>)
91091      */
91092     growMax: 1000,
91093
91094     /**
91095      * @cfg {String} growAppend
91096      * A string that will be appended to the field's current value for the purposes of calculating the target
91097      * field size. Only used when the {@link #grow} config is <tt>true</tt>. Defaults to a newline for TextArea
91098      * to ensure there is always a space below the current line.
91099      */
91100     growAppend: '\n-',
91101
91102     /**
91103      * @cfg {Number} cols An initial value for the 'cols' attribute on the textarea element. This is only
91104      * used if the component has no configured {@link #width} and is not given a width by its container's
91105      * layout. Defaults to <tt>20</tt>.
91106      */
91107     cols: 20,
91108
91109     /**
91110      * @cfg {Number} cols An initial value for the 'cols' attribute on the textarea element. This is only
91111      * used if the component has no configured {@link #width} and is not given a width by its container's
91112      * layout. Defaults to <tt>4</tt>.
91113      */
91114     rows: 4,
91115
91116     /**
91117      * @cfg {Boolean} enterIsSpecial
91118      * True if you want the enter key to be classed as a <tt>special</tt> key. Special keys are generally navigation
91119      * keys (arrows, space, enter). Setting the config property to <tt>true</tt> would mean that you could not insert
91120      * returns into the textarea.
91121      * (defaults to <tt>false</tt>)
91122      */
91123     enterIsSpecial: false,
91124
91125     /**
91126      * @cfg {Boolean} preventScrollbars <tt>true</tt> to prevent scrollbars from appearing regardless of how much text is
91127      * in the field. This option is only relevant when {@link #grow} is <tt>true</tt>. Equivalent to setting overflow: hidden, defaults to
91128      * <tt>false</tt>.
91129      */
91130     preventScrollbars: false,
91131
91132     // private
91133     componentLayout: 'textareafield',
91134
91135     // private
91136     onRender: function(ct, position) {
91137         var me = this;
91138         Ext.applyIf(me.subTplData, {
91139             cols: me.cols,
91140             rows: me.rows
91141         });
91142
91143         me.callParent(arguments);
91144     },
91145
91146     // private
91147     afterRender: function(){
91148         var me = this;
91149
91150         me.callParent(arguments);
91151
91152         if (me.grow) {
91153             if (me.preventScrollbars) {
91154                 me.inputEl.setStyle('overflow', 'hidden');
91155             }
91156             me.inputEl.setHeight(me.growMin);
91157         }
91158     },
91159
91160     // private
91161     fireKey: function(e) {
91162         if (e.isSpecialKey() && (this.enterIsSpecial || (e.getKey() !== e.ENTER || e.hasModifier()))) {
91163             this.fireEvent('specialkey', this, e);
91164         }
91165     },
91166
91167     /**
91168      * Automatically grows the field to accomodate the height of the text up to the maximum field height allowed.
91169      * This only takes effect if <tt>{@link #grow} = true</tt>, and fires the {@link #autosize} event if
91170      * the height changes.
91171      */
91172     autoSize: function() {
91173         var me = this,
91174             height;
91175
91176         if (me.grow && me.rendered) {
91177             me.doComponentLayout();
91178             height = me.inputEl.getHeight();
91179             if (height !== me.lastInputHeight) {
91180                 me.fireEvent('autosize', height);
91181                 me.lastInputHeight = height;
91182             }
91183         }
91184     },
91185
91186     // private
91187     initAria: function() {
91188         this.callParent(arguments);
91189         this.getActionEl().dom.setAttribute('aria-multiline', true);
91190     },
91191
91192     /**
91193      * @protected override
91194      * To get the natural width of the textarea element, we do a simple calculation based on the
91195      * 'cols' config. We use hard-coded numbers to approximate what browsers do natively,
91196      * to avoid having to read any styles which would hurt performance.
91197      */
91198     getBodyNaturalWidth: function() {
91199         return Math.round(this.cols * 6.5) + 20;
91200     }
91201
91202 });
91203
91204
91205 /**
91206  * @class Ext.window.MessageBox
91207  * @extends Ext.window.Window
91208
91209 Utility class for generating different styles of message boxes.  The singleton instance, `Ext.Msg` can also be used.
91210 Note that a MessageBox is asynchronous.  Unlike a regular JavaScript `alert` (which will halt
91211 browser execution), showing a MessageBox will not cause the code to stop.  For this reason, if you have code
91212 that should only run *after* some user feedback from the MessageBox, you must use a callback function
91213 (see the `function` parameter for {@link #show} for more details).
91214
91215 {@img Ext.window.MessageBox/messagebox1.png alert MessageBox}
91216 {@img Ext.window.MessageBox/messagebox2.png prompt MessageBox}
91217 {@img Ext.window.MessageBox/messagebox3.png show MessageBox}
91218 #Example usage:#
91219
91220     // Basic alert:
91221     Ext.Msg.alert('Status', 'Changes saved successfully.');
91222
91223     // Prompt for user data and process the result using a callback:
91224     Ext.Msg.prompt('Name', 'Please enter your name:', function(btn, text){
91225         if (btn == 'ok'){
91226             // process text value and close...
91227         }
91228     });
91229
91230     // Show a dialog using config options:
91231     Ext.Msg.show({
91232          title:'Save Changes?',
91233          msg: 'You are closing a tab that has unsaved changes. Would you like to save your changes?',
91234          buttons: Ext.Msg.YESNOCANCEL,
91235          fn: processResult,
91236          animateTarget: 'elId',
91237          icon: Ext.window.MessageBox.QUESTION
91238     });
91239
91240  * @markdown
91241  * @singleton
91242  * @xtype messagebox
91243  */
91244 Ext.define('Ext.window.MessageBox', {
91245     extend: 'Ext.window.Window',
91246
91247     requires: [
91248         'Ext.toolbar.Toolbar',
91249         'Ext.form.field.Text',
91250         'Ext.form.field.TextArea',
91251         'Ext.button.Button',
91252         'Ext.layout.container.Anchor',
91253         'Ext.layout.container.HBox',
91254         'Ext.ProgressBar'
91255     ],
91256     
91257     alternateClassName: 'Ext.MessageBox',
91258
91259     alias: 'widget.messagebox',
91260
91261     /**
91262      * Button config that displays a single OK button
91263      * @type Number
91264      */
91265     OK : 1,
91266     /**
91267      * Button config that displays a single Yes button
91268      * @type Number
91269      */
91270     YES : 2,
91271     /**
91272      * Button config that displays a single No button
91273      * @type Number
91274      */
91275     NO : 4,
91276     /**
91277      * Button config that displays a single Cancel button
91278      * @type Number
91279      */
91280     CANCEL : 8,
91281     /**
91282      * Button config that displays OK and Cancel buttons
91283      * @type Number
91284      */
91285     OKCANCEL : 9,
91286     /**
91287      * Button config that displays Yes and No buttons
91288      * @type Number
91289      */
91290     YESNO : 6,
91291     /**
91292      * Button config that displays Yes, No and Cancel buttons
91293      * @type Number
91294      */
91295     YESNOCANCEL : 14,
91296     /**
91297      * The CSS class that provides the INFO icon image
91298      * @type String
91299      */
91300     INFO : 'ext-mb-info',
91301     /**
91302      * The CSS class that provides the WARNING icon image
91303      * @type String
91304      */
91305     WARNING : 'ext-mb-warning',
91306     /**
91307      * The CSS class that provides the QUESTION icon image
91308      * @type String
91309      */
91310     QUESTION : 'ext-mb-question',
91311     /**
91312      * The CSS class that provides the ERROR icon image
91313      * @type String
91314      */
91315     ERROR : 'ext-mb-error',
91316
91317     // hide it by offsets. Windows are hidden on render by default.
91318     hideMode: 'offsets',
91319     closeAction: 'hide',
91320     resizable: false,
91321     title: '&#160;',
91322
91323     width: 600,
91324     height: 500,
91325     minWidth: 250,
91326     maxWidth: 600,
91327     minHeight: 110,
91328     maxHeight: 500,
91329     constrain: true,
91330
91331     cls: Ext.baseCSSPrefix + 'message-box',
91332
91333     layout: {
91334         type: 'anchor'
91335     },
91336
91337     /**
91338      * The default height in pixels of the message box's multiline textarea if displayed (defaults to 75)
91339      * @type Number
91340      */
91341     defaultTextHeight : 75,
91342     /**
91343      * The minimum width in pixels of the message box if it is a progress-style dialog.  This is useful
91344      * for setting a different minimum width than text-only dialogs may need (defaults to 250).
91345      * @type Number
91346      */
91347     minProgressWidth : 250,
91348     /**
91349      * The minimum width in pixels of the message box if it is a prompt dialog.  This is useful
91350      * for setting a different minimum width than text-only dialogs may need (defaults to 250).
91351      * @type Number
91352      */
91353     minPromptWidth: 250,
91354     /**
91355      * An object containing the default button text strings that can be overriden for localized language support.
91356      * Supported properties are: ok, cancel, yes and no.  Generally you should include a locale-specific
91357      * resource file for handling language support across the framework.
91358      * Customize the default text like so: Ext.window.MessageBox.buttonText.yes = "oui"; //french
91359      * @type Object
91360      */
91361     buttonText: {
91362         ok: 'OK',
91363         yes: 'Yes',
91364         no: 'No',
91365         cancel: 'Cancel'
91366     },
91367
91368     buttonIds: [
91369         'ok', 'yes', 'no', 'cancel'
91370     ],
91371
91372     titleText: {
91373         confirm: 'Confirm',
91374         prompt: 'Prompt',
91375         wait: 'Loading...',
91376         alert: 'Attention'
91377     },
91378     
91379     iconHeight: 35,
91380
91381     makeButton: function(btnIdx) {
91382         var btnId = this.buttonIds[btnIdx];
91383         return Ext.create('Ext.button.Button', {
91384             handler: this.btnCallback,
91385             itemId: btnId,
91386             scope: this,
91387             text: this.buttonText[btnId],
91388             minWidth: 75
91389         });
91390     },
91391
91392     btnCallback: function(btn) {
91393         var me = this,
91394             value,
91395             field;
91396
91397         if (me.cfg.prompt || me.cfg.multiline) {
91398             if (me.cfg.multiline) {
91399                 field = me.textArea;
91400             } else {
91401                 field = me.textField;
91402             }
91403             value = field.getValue();
91404             field.reset();
91405         }
91406
91407         // Important not to have focus remain in the hidden Window; Interferes with DnD.
91408         btn.blur();
91409         me.hide();
91410         me.userCallback(btn.itemId, value, me.cfg);
91411     },
91412
91413     hide: function() {
91414         var me = this;
91415         me.dd.endDrag();
91416         me.progressBar.reset();
91417         me.removeCls(me.cfg.cls);
91418         me.callParent();
91419     },
91420
91421     initComponent: function() {
91422         var me = this,
91423             i, button;
91424
91425         me.title = '&#160;';
91426
91427         me.topContainer = Ext.create('Ext.container.Container', {
91428             anchor: '100%',
91429             style: {
91430                 padding: '10px',
91431                 overflow: 'hidden'
91432             },
91433             items: [
91434                 me.iconComponent = Ext.create('Ext.Component', {
91435                     cls: 'ext-mb-icon',
91436                     width: 50,
91437                     height: me.iconHeight,
91438                     style: {
91439                         'float': 'left'
91440                     }
91441                 }),
91442                 me.promptContainer = Ext.create('Ext.container.Container', {
91443                     layout: {
91444                         type: 'anchor'
91445                     },
91446                     items: [
91447                         me.msg = Ext.create('Ext.Component', {
91448                             autoEl: { tag: 'span' },
91449                             cls: 'ext-mb-text'
91450                         }),
91451                         me.textField = Ext.create('Ext.form.field.Text', {
91452                             anchor: '100%',
91453                             enableKeyEvents: true,
91454                             listeners: {
91455                                 keydown: me.onPromptKey,
91456                                 scope: me
91457                             }
91458                         }),
91459                         me.textArea = Ext.create('Ext.form.field.TextArea', {
91460                             anchor: '100%',
91461                             height: 75
91462                         })
91463                     ]
91464                 })
91465             ]
91466         });
91467         me.progressBar = Ext.create('Ext.ProgressBar', {
91468             anchor: '-10',
91469             style: 'margin-left:10px'
91470         });
91471
91472         me.items = [me.topContainer, me.progressBar];
91473
91474         // Create the buttons based upon passed bitwise config
91475         me.msgButtons = [];
91476         for (i = 0; i < 4; i++) {
91477             button = me.makeButton(i);
91478             me.msgButtons[button.itemId] = button;
91479             me.msgButtons.push(button);
91480         }
91481         me.bottomTb = Ext.create('Ext.toolbar.Toolbar', {
91482             ui: 'footer',
91483             dock: 'bottom',
91484             layout: {
91485                 pack: 'center'
91486             },
91487             items: [
91488                 me.msgButtons[0],
91489                 me.msgButtons[1],
91490                 me.msgButtons[2],
91491                 me.msgButtons[3]
91492             ]
91493         });
91494         me.dockedItems = [me.bottomTb];
91495
91496         me.callParent();
91497     },
91498
91499     onPromptKey: function(textField, e) {
91500         var me = this,
91501             blur;
91502             
91503         if (e.keyCode === Ext.EventObject.RETURN || e.keyCode === 10) {
91504             if (me.msgButtons.ok.isVisible()) {
91505                 blur = true;
91506                 me.msgButtons.ok.handler.call(me, me.msgButtons.ok);
91507             } else if (me.msgButtons.yes.isVisible()) {
91508                 me.msgButtons.yes.handler.call(me, me.msgButtons.yes);
91509                 blur = true;
91510             }
91511             
91512             if (blur) {
91513                 me.textField.blur();
91514             }
91515         }
91516     },
91517
91518     reconfigure: function(cfg) {
91519         var me = this,
91520             buttons = cfg.buttons || 0,
91521             hideToolbar = true,
91522             initialWidth = me.maxWidth,
91523             i;
91524
91525         cfg = cfg || {};
91526         me.cfg = cfg;
91527         if (cfg.width) {
91528             initialWidth = cfg.width;
91529         }
91530
91531         // Default to allowing the Window to take focus.
91532         delete me.defaultFocus;
91533         
91534         // clear any old animateTarget
91535         me.animateTarget = cfg.animateTarget || undefined;
91536
91537         // Defaults to modal
91538         me.modal = cfg.modal !== false;
91539
91540         // Show the title
91541         if (cfg.title) {
91542             me.setTitle(cfg.title||'&#160;');
91543         }
91544
91545         if (!me.rendered) {
91546             me.width = initialWidth;
91547             me.render(Ext.getBody());
91548         } else {
91549             me.setSize(initialWidth, me.maxHeight);
91550         }
91551         me.setPosition(-10000, -10000);
91552
91553         // Hide or show the close tool
91554         me.closable = cfg.closable && !cfg.wait;
91555         if (cfg.closable === false) {
91556             me.tools.close.hide();
91557         } else {
91558             me.tools.close.show();
91559         }
91560
91561         // Hide or show the header
91562         if (!cfg.title && !me.closable) {
91563             me.header.hide();
91564         } else {
91565             me.header.show();
91566         }
91567
91568         // Default to dynamic drag: drag the window, not a ghost
91569         me.liveDrag = !cfg.proxyDrag;
91570
91571         // wrap the user callback
91572         me.userCallback = Ext.Function.bind(cfg.callback ||cfg.fn || Ext.emptyFn, cfg.scope || Ext.global);
91573
91574         // Hide or show the icon Component
91575         me.setIcon(cfg.icon);
91576
91577         // Hide or show the message area
91578         if (cfg.msg) {
91579             me.msg.update(cfg.msg);
91580             me.msg.show();
91581         } else {
91582             me.msg.hide();
91583         }
91584
91585         // Hide or show the input field
91586         if (cfg.prompt || cfg.multiline) {
91587             me.multiline = cfg.multiline;
91588             if (cfg.multiline) {
91589                 me.textArea.setValue(cfg.value);
91590                 me.textArea.setHeight(cfg.defaultTextHeight || me.defaultTextHeight);
91591                 me.textArea.show();
91592                 me.textField.hide();
91593                 me.defaultFocus = me.textArea;
91594             } else {
91595                 me.textField.setValue(cfg.value);
91596                 me.textArea.hide();
91597                 me.textField.show();
91598                 me.defaultFocus = me.textField;
91599             }
91600         } else {
91601             me.textArea.hide();
91602             me.textField.hide();
91603         }
91604
91605         // Hide or show the progress bar
91606         if (cfg.progress || cfg.wait) {
91607             me.progressBar.show();
91608             me.updateProgress(0, cfg.progressText);
91609             if(cfg.wait === true){
91610                 me.progressBar.wait(cfg.waitConfig);
91611             }
91612         } else {
91613             me.progressBar.hide();
91614         }
91615
91616         // Hide or show buttons depending on flag value sent.
91617         for (i = 0; i < 4; i++) {
91618             if (buttons & Math.pow(2, i)) {
91619
91620                 // Default to focus on the first visible button if focus not already set
91621                 if (!me.defaultFocus) {
91622                     me.defaultFocus = me.msgButtons[i];
91623                 }
91624                 me.msgButtons[i].show();
91625                 hideToolbar = false;
91626             } else {
91627                 me.msgButtons[i].hide();
91628             }
91629         }
91630
91631         // Hide toolbar if no buttons to show
91632         if (hideToolbar) {
91633             me.bottomTb.hide();
91634         } else {
91635             me.bottomTb.show();
91636         }
91637         me.hidden = true;
91638     },
91639
91640     /**
91641      * Displays a new message box, or reinitializes an existing message box, based on the config options
91642      * passed in. All display functions (e.g. prompt, alert, etc.) on MessageBox call this function internally,
91643      * although those calls are basic shortcuts and do not support all of the config options allowed here.
91644      * @param {Object} config The following config options are supported: <ul>
91645      * <li><b>animateTarget</b> : String/Element<div class="sub-desc">An id or Element from which the message box should animate as it
91646      * opens and closes (defaults to undefined)</div></li>
91647      * <li><b>buttons</b> : Number<div class="sub-desc">A bitwise button specifier consisting of the sum of any of the following constants:<ul>
91648      * <li>Ext.window.MessageBox.OK</li>
91649      * <li>Ext.window.MessageBox.YES</li>
91650      * <li>Ext.window.MessageBox.NO</li>
91651      * <li>Ext.window.MessageBox.CANCEL</li>
91652      * </ul>Or false to not show any buttons (defaults to false)</div></li>
91653      * <li><b>closable</b> : Boolean<div class="sub-desc">False to hide the top-right close button (defaults to true). Note that
91654      * progress and wait dialogs will ignore this property and always hide the close button as they can only
91655      * be closed programmatically.</div></li>
91656      * <li><b>cls</b> : String<div class="sub-desc">A custom CSS class to apply to the message box's container element</div></li>
91657      * <li><b>defaultTextHeight</b> : Number<div class="sub-desc">The default height in pixels of the message box's multiline textarea
91658      * if displayed (defaults to 75)</div></li>
91659      * <li><b>fn</b> : Function<div class="sub-desc">A callback function which is called when the dialog is dismissed either
91660      * by clicking on the configured buttons, or on the dialog close button, or by pressing
91661      * the return button to enter input.
91662      * <p>Progress and wait dialogs will ignore this option since they do not respond to user
91663      * actions and can only be closed programmatically, so any required function should be called
91664      * by the same code after it closes the dialog. Parameters passed:<ul>
91665      * <li><b>buttonId</b> : String<div class="sub-desc">The ID of the button pressed, one of:<div class="sub-desc"><ul>
91666      * <li><tt>ok</tt></li>
91667      * <li><tt>yes</tt></li>
91668      * <li><tt>no</tt></li>
91669      * <li><tt>cancel</tt></li>
91670      * </ul></div></div></li>
91671      * <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>
91672      * or <tt><a href="#show-option-multiline" ext:member="show-option-multiline" ext:cls="Ext.window.MessageBox">multiline</a></tt> is true</div></li>
91673      * <li><b>opt</b> : Object<div class="sub-desc">The config object passed to show.</div></li>
91674      * </ul></p></div></li>
91675      * <li><b>scope</b> : Object<div class="sub-desc">The scope (<code>this</code> reference) in which the function will be executed.</div></li>
91676      * <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
91677      * dialog (e.g. Ext.window.MessageBox.WARNING or 'custom-class') (defaults to '')</div></li>
91678      * <li><b>iconCls</b> : String<div class="sub-desc">The standard {@link Ext.window.Window#iconCls} to
91679      * add an optional header icon (defaults to '')</div></li>
91680      * <li><b>maxWidth</b> : Number<div class="sub-desc">The maximum width in pixels of the message box (defaults to 600)</div></li>
91681      * <li><b>minWidth</b> : Number<div class="sub-desc">The minimum width in pixels of the message box (defaults to 100)</div></li>
91682      * <li><b>modal</b> : Boolean<div class="sub-desc">False to allow user interaction with the page while the message box is
91683      * displayed (defaults to true)</div></li>
91684      * <li><b>msg</b> : String<div class="sub-desc">A string that will replace the existing message box body text (defaults to the
91685      * XHTML-compliant non-breaking space character '&amp;#160;')</div></li>
91686      * <li><a id="show-option-multiline"></a><b>multiline</b> : Boolean<div class="sub-desc">
91687      * True to prompt the user to enter multi-line text (defaults to false)</div></li>
91688      * <li><b>progress</b> : Boolean<div class="sub-desc">True to display a progress bar (defaults to false)</div></li>
91689      * <li><b>progressText</b> : String<div class="sub-desc">The text to display inside the progress bar if progress = true (defaults to '')</div></li>
91690      * <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>
91691      * <li><b>proxyDrag</b> : Boolean<div class="sub-desc">True to display a lightweight proxy while dragging (defaults to false)</div></li>
91692      * <li><b>title</b> : String<div class="sub-desc">The title text</div></li>
91693      * <li><b>value</b> : String<div class="sub-desc">The string value to set into the active textbox element if displayed</div></li>
91694      * <li><b>wait</b> : Boolean<div class="sub-desc">True to display a progress bar (defaults to false)</div></li>
91695      * <li><b>waitConfig</b> : Object<div class="sub-desc">A {@link Ext.ProgressBar#waitConfig} object (applies only if wait = true)</div></li>
91696      * <li><b>width</b> : Number<div class="sub-desc">The width of the dialog in pixels</div></li>
91697      * </ul>
91698      * Example usage:
91699      * <pre><code>
91700 Ext.Msg.show({
91701 title: 'Address',
91702 msg: 'Please enter your address:',
91703 width: 300,
91704 buttons: Ext.window.MessageBox.OKCANCEL,
91705 multiline: true,
91706 fn: saveAddress,
91707 animateTarget: 'addAddressBtn',
91708 icon: Ext.window.MessageBox.INFO
91709 });
91710 </code></pre>
91711      * @return {Ext.window.MessageBox} this
91712      */
91713     show: function(cfg) {
91714         var me = this;
91715             
91716         me.reconfigure(cfg);
91717         me.addCls(cfg.cls);
91718         if (cfg.animateTarget) {
91719             me.doAutoSize(false);
91720             me.callParent();
91721         } else {
91722             me.callParent();
91723             me.doAutoSize(true);
91724         }
91725         return me;
91726     },
91727     
91728     afterShow: function(){
91729         if (this.animateTarget) {
91730             this.center();
91731         }    
91732         this.callParent(arguments);
91733     },
91734
91735     doAutoSize: function(center) {
91736         var me = this,
91737             icon = me.iconComponent,
91738             iconHeight = me.iconHeight;
91739
91740         if (!Ext.isDefined(me.frameWidth)) {
91741             me.frameWidth = me.el.getWidth() - me.body.getWidth();
91742         }
91743         
91744         // reset to the original dimensions
91745         icon.setHeight(iconHeight);
91746
91747         // Allow per-invocation override of minWidth
91748         me.minWidth = me.cfg.minWidth || Ext.getClass(this).prototype.minWidth;
91749
91750         // Set best possible size based upon allowing the text to wrap in the maximized Window, and
91751         // then constraining it to within the max with. Then adding up constituent element heights.
91752         me.topContainer.doLayout();
91753         if (Ext.isIE6 || Ext.isIEQuirks) {
91754             // In IE quirks, the initial full width of the prompt fields will prevent the container element
91755             // from collapsing once sized down, so temporarily force them to a small width. They'll get
91756             // layed out to their final width later when setting the final window size.
91757             me.textField.setCalculatedSize(9);
91758             me.textArea.setCalculatedSize(9);
91759         }
91760         var width = me.cfg.width || me.msg.getWidth() + icon.getWidth() + 25, /* topContainer's layout padding */
91761             height = (me.header.rendered ? me.header.getHeight() : 0) +
91762             Math.max(me.promptContainer.getHeight(), icon.getHeight()) +
91763             me.progressBar.getHeight() +
91764             (me.bottomTb.rendered ? me.bottomTb.getHeight() : 0) + 20 ;/* topContainer's layout padding */
91765
91766         // Update to the size of the content, this way the text won't wrap under the icon.
91767         icon.setHeight(Math.max(iconHeight, me.msg.getHeight()));
91768         me.setSize(width + me.frameWidth, height + me.frameWidth);
91769         if (center) {
91770             me.center();
91771         }
91772         return me;
91773     },
91774
91775     updateText: function(text) {
91776         this.msg.update(text);
91777         return this.doAutoSize(true);
91778     },
91779
91780     /**
91781      * Adds the specified icon to the dialog.  By default, the class 'ext-mb-icon' is applied for default
91782      * styling, and the class passed in is expected to supply the background image url. Pass in empty string ('')
91783      * to clear any existing icon. This method must be called before the MessageBox is shown.
91784      * The following built-in icon classes are supported, but you can also pass in a custom class name:
91785      * <pre>
91786 Ext.window.MessageBox.INFO
91787 Ext.window.MessageBox.WARNING
91788 Ext.window.MessageBox.QUESTION
91789 Ext.window.MessageBox.ERROR
91790      *</pre>
91791      * @param {String} icon A CSS classname specifying the icon's background image url, or empty string to clear the icon
91792      * @return {Ext.window.MessageBox} this
91793      */
91794     setIcon : function(icon) {
91795         var me = this;
91796         me.iconComponent.removeCls(me.iconCls);
91797         if (icon) {
91798             me.iconComponent.show();
91799             me.iconComponent.addCls(Ext.baseCSSPrefix + 'dlg-icon');
91800             me.iconComponent.addCls(me.iconCls = icon);
91801         } else {
91802             me.iconComponent.removeCls(Ext.baseCSSPrefix + 'dlg-icon');
91803             me.iconComponent.hide();
91804         }
91805         return me;
91806     },
91807
91808     /**
91809      * Updates a progress-style message box's text and progress bar. Only relevant on message boxes
91810      * initiated via {@link Ext.window.MessageBox#progress} or {@link Ext.window.MessageBox#wait},
91811      * or by calling {@link Ext.window.MessageBox#show} with progress: true.
91812      * @param {Number} value Any number between 0 and 1 (e.g., .5, defaults to 0)
91813      * @param {String} progressText The progress text to display inside the progress bar (defaults to '')
91814      * @param {String} msg The message box's body text is replaced with the specified string (defaults to undefined
91815      * so that any existing body text will not get overwritten by default unless a new value is passed in)
91816      * @return {Ext.window.MessageBox} this
91817      */
91818     updateProgress : function(value, progressText, msg){
91819         this.progressBar.updateProgress(value, progressText);
91820         if (msg){
91821             this.updateText(msg);
91822         }
91823         return this;
91824     },
91825
91826     onEsc: function() {
91827         if (this.closable !== false) {
91828             this.callParent(arguments);
91829         }
91830     },
91831
91832     /**
91833      * Displays a confirmation message box with Yes and No buttons (comparable to JavaScript's confirm).
91834      * If a callback function is passed it will be called after the user clicks either button,
91835      * and the id of the button that was clicked will be passed as the only parameter to the callback
91836      * (could also be the top-right close button).
91837      * @param {String} title The title bar text
91838      * @param {String} msg The message box body text
91839      * @param {Function} fn (optional) The callback function invoked after the message box is closed
91840      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the callback is executed. Defaults to the browser wnidow.
91841      * @return {Ext.window.MessageBox} this
91842      */
91843     confirm: function(cfg, msg, fn, scope) {
91844         if (Ext.isString(cfg)) {
91845             cfg = {
91846                 title: cfg,
91847                 icon: 'ext-mb-question',
91848                 msg: msg,
91849                 buttons: this.YESNO,
91850                 callback: fn,
91851                 scope: scope
91852             };
91853         }
91854         return this.show(cfg);
91855     },
91856
91857     /**
91858      * Displays a message box with OK and Cancel buttons prompting the user to enter some text (comparable to JavaScript's prompt).
91859      * The prompt can be a single-line or multi-line textbox.  If a callback function is passed it will be called after the user
91860      * clicks either button, and the id of the button that was clicked (could also be the top-right
91861      * close button) and the text that was entered will be passed as the two parameters to the callback.
91862      * @param {String} title The title bar text
91863      * @param {String} msg The message box body text
91864      * @param {Function} fn (optional) The callback function invoked after the message box is closed
91865      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the callback is executed. Defaults to the browser wnidow.
91866      * @param {Boolean/Number} multiline (optional) True to create a multiline textbox using the defaultTextHeight
91867      * property, or the height in pixels to create the textbox (defaults to false / single-line)
91868      * @param {String} value (optional) Default value of the text input element (defaults to '')
91869      * @return {Ext.window.MessageBox} this
91870      */
91871     prompt : function(cfg, msg, fn, scope, multiline, value){
91872         if (Ext.isString(cfg)) {
91873             cfg = {
91874                 prompt: true,
91875                 title: cfg,
91876                 minWidth: this.minPromptWidth,
91877                 msg: msg,
91878                 buttons: this.OKCANCEL,
91879                 callback: fn,
91880                 scope: scope,
91881                 multiline: multiline,
91882                 value: value
91883             };
91884         }
91885         return this.show(cfg);
91886     },
91887
91888     /**
91889      * Displays a message box with an infinitely auto-updating progress bar.  This can be used to block user
91890      * interaction while waiting for a long-running process to complete that does not have defined intervals.
91891      * You are responsible for closing the message box when the process is complete.
91892      * @param {String} msg The message box body text
91893      * @param {String} title (optional) The title bar text
91894      * @param {Object} config (optional) A {@link Ext.ProgressBar#waitConfig} object
91895      * @return {Ext.window.MessageBox} this
91896      */
91897     wait : function(cfg, title, config){
91898         if (Ext.isString(cfg)) {
91899             cfg = {
91900                 title : title,
91901                 msg : cfg,
91902                 closable: false,
91903                 wait: true,
91904                 modal: true,
91905                 minWidth: this.minProgressWidth,
91906                 waitConfig: config
91907             };
91908         }
91909         return this.show(cfg);
91910     },
91911
91912     /**
91913      * Displays a standard read-only message box with an OK button (comparable to the basic JavaScript alert prompt).
91914      * If a callback function is passed it will be called after the user clicks the button, and the
91915      * id of the button that was clicked will be passed as the only parameter to the callback
91916      * (could also be the top-right close button).
91917      * @param {String} title The title bar text
91918      * @param {String} msg The message box body text
91919      * @param {Function} fn (optional) The callback function invoked after the message box is closed
91920      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the callback is executed. Defaults to the browser wnidow.
91921      * @return {Ext.window.MessageBox} this
91922      */
91923     alert: function(cfg, msg, fn, scope) {
91924         if (Ext.isString(cfg)) {
91925             cfg = {
91926                 title : cfg,
91927                 msg : msg,
91928                 buttons: this.OK,
91929                 fn: fn,
91930                 scope : scope,
91931                 minWidth: this.minWidth
91932             };
91933         }
91934         return this.show(cfg);
91935     },
91936
91937     /**
91938      * Displays a message box with a progress bar.  This message box has no buttons and is not closeable by
91939      * the user.  You are responsible for updating the progress bar as needed via {@link Ext.window.MessageBox#updateProgress}
91940      * and closing the message box when the process is complete.
91941      * @param {String} title The title bar text
91942      * @param {String} msg The message box body text
91943      * @param {String} progressText (optional) The text to display inside the progress bar (defaults to '')
91944      * @return {Ext.window.MessageBox} this
91945      */
91946     progress : function(cfg, msg, progressText){
91947         if (Ext.isString(cfg)) {
91948             cfg = {
91949                 title: cfg,
91950                 msg: msg,
91951                 progressText: progressText
91952             };
91953         }
91954         return this.show(cfg);
91955     }
91956 }, function() {
91957     Ext.MessageBox = Ext.Msg = new this();
91958 });
91959 /**
91960  * @class Ext.form.Basic
91961  * @extends Ext.util.Observable
91962
91963 Provides input field management, validation, submission, and form loading services for the collection
91964 of {@link Ext.form.field.Field Field} instances within a {@link Ext.container.Container}. It is recommended
91965 that you use a {@link Ext.form.Panel} as the form container, as that has logic to automatically
91966 hook up an instance of {@link Ext.form.Basic} (plus other conveniences related to field configuration.)
91967
91968 #Form Actions#
91969
91970 The Basic class delegates the handling of form loads and submits to instances of {@link Ext.form.action.Action}.
91971 See the various Action implementations for specific details of each one's functionality, as well as the
91972 documentation for {@link #doAction} which details the configuration options that can be specified in
91973 each action call.
91974
91975 The default submit Action is {@link Ext.form.action.Submit}, which uses an Ajax request to submit the
91976 form's values to a configured URL. To enable normal browser submission of an Ext form, use the
91977 {@link #standardSubmit} config option.
91978
91979 Note: File uploads are not performed using normal 'Ajax' techniques; see the description for
91980 {@link #hasUpload} for details.
91981
91982 #Example usage:#
91983
91984     Ext.create('Ext.form.Panel', {
91985         title: 'Basic Form',
91986         renderTo: Ext.getBody(),
91987         bodyPadding: 5,
91988         width: 350,
91989
91990         // Any configuration items here will be automatically passed along to
91991         // the Ext.form.Basic instance when it gets created.
91992
91993         // The form will submit an AJAX request to this URL when submitted
91994         url: 'save-form.php',
91995
91996         items: [{
91997             fieldLabel: 'Field',
91998             name: 'theField'
91999         }],
92000
92001         buttons: [{
92002             text: 'Submit',
92003             handler: function() {
92004                 // The getForm() method returns the Ext.form.Basic instance:
92005                 var form = this.up('form').getForm();
92006                 if (form.isValid()) {
92007                     // Submit the Ajax request and handle the response
92008                     form.submit({
92009                         success: function(form, action) {
92010                            Ext.Msg.alert('Success', action.result.msg);
92011                         },
92012                         failure: function(form, action) {
92013                             Ext.Msg.alert('Failed', action.result.msg);
92014                         }
92015                     });
92016                 }
92017             }
92018         }]
92019     });
92020
92021  * @constructor
92022  * @param {Ext.container.Container} owner The component that is the container for the form, usually a {@link Ext.form.Panel}
92023  * @param {Object} config Configuration options. These are normally specified in the config to the
92024  * {@link Ext.form.Panel} constructor, which passes them along to the BasicForm automatically.
92025  *
92026  * @markdown
92027  * @docauthor Jason Johnston <jason@sencha.com>
92028  */
92029
92030
92031
92032 Ext.define('Ext.form.Basic', {
92033     extend: 'Ext.util.Observable',
92034     alternateClassName: 'Ext.form.BasicForm',
92035     requires: ['Ext.util.MixedCollection', 'Ext.form.action.Load', 'Ext.form.action.Submit',
92036                'Ext.window.MessageBox', 'Ext.data.Errors'],
92037
92038     constructor: function(owner, config) {
92039         var me = this,
92040             onItemAddOrRemove = me.onItemAddOrRemove;
92041
92042         /**
92043          * @property owner
92044          * @type Ext.container.Container
92045          * The container component to which this BasicForm is attached.
92046          */
92047         me.owner = owner;
92048
92049         // Listen for addition/removal of fields in the owner container
92050         me.mon(owner, {
92051             add: onItemAddOrRemove,
92052             remove: onItemAddOrRemove,
92053             scope: me
92054         });
92055
92056         Ext.apply(me, config);
92057
92058         // Normalize the paramOrder to an Array
92059         if (Ext.isString(me.paramOrder)) {
92060             me.paramOrder = me.paramOrder.split(/[\s,|]/);
92061         }
92062
92063         me.addEvents(
92064             /**
92065              * @event beforeaction
92066              * Fires before any action is performed. Return false to cancel the action.
92067              * @param {Ext.form.Basic} this
92068              * @param {Ext.form.action.Action} action The {@link Ext.form.action.Action} to be performed
92069              */
92070             'beforeaction',
92071             /**
92072              * @event actionfailed
92073              * Fires when an action fails.
92074              * @param {Ext.form.Basic} this
92075              * @param {Ext.form.action.Action} action The {@link Ext.form.action.Action} that failed
92076              */
92077             'actionfailed',
92078             /**
92079              * @event actioncomplete
92080              * Fires when an action is completed.
92081              * @param {Ext.form.Basic} this
92082              * @param {Ext.form.action.Action} action The {@link Ext.form.action.Action} that completed
92083              */
92084             'actioncomplete',
92085             /**
92086              * @event validitychange
92087              * Fires when the validity of the entire form changes.
92088              * @param {Ext.form.Basic} this
92089              * @param {Boolean} valid <tt>true</tt> if the form is now valid, <tt>false</tt> if it is now invalid.
92090              */
92091             'validitychange',
92092             /**
92093              * @event dirtychange
92094              * Fires when the dirty state of the entire form changes.
92095              * @param {Ext.form.Basic} this
92096              * @param {Boolean} dirty <tt>true</tt> if the form is now dirty, <tt>false</tt> if it is no longer dirty.
92097              */
92098             'dirtychange'
92099         );
92100         me.callParent();
92101     },
92102
92103     /**
92104      * Do any post constructor initialization
92105      * @private
92106      */
92107     initialize: function(){
92108         this.initialized = true;
92109         this.onValidityChange(!this.hasInvalidField());
92110     },
92111
92112     /**
92113      * @cfg {String} method
92114      * The request method to use (GET or POST) for form actions if one isn't supplied in the action options.
92115      */
92116     /**
92117      * @cfg {Ext.data.reader.Reader} reader
92118      * An Ext.data.DataReader (e.g. {@link Ext.data.reader.Xml}) to be used to read
92119      * data when executing 'load' actions. This is optional as there is built-in
92120      * support for processing JSON responses.
92121      */
92122     /**
92123      * @cfg {Ext.data.reader.Reader} errorReader
92124      * <p>An Ext.data.DataReader (e.g. {@link Ext.data.reader.Xml}) to be used to
92125      * read field error messages returned from 'submit' actions. This is optional
92126      * as there is built-in support for processing JSON responses.</p>
92127      * <p>The Records which provide messages for the invalid Fields must use the
92128      * Field name (or id) as the Record ID, and must contain a field called 'msg'
92129      * which contains the error message.</p>
92130      * <p>The errorReader does not have to be a full-blown implementation of a
92131      * Reader. It simply needs to implement a <tt>read(xhr)</tt> function
92132      * which returns an Array of Records in an object with the following
92133      * structure:</p><pre><code>
92134 {
92135     records: recordArray
92136 }
92137 </code></pre>
92138      */
92139
92140     /**
92141      * @cfg {String} url
92142      * The URL to use for form actions if one isn't supplied in the
92143      * {@link #doAction doAction} options.
92144      */
92145
92146     /**
92147      * @cfg {Object} baseParams
92148      * <p>Parameters to pass with all requests. e.g. baseParams: {id: '123', foo: 'bar'}.</p>
92149      * <p>Parameters are encoded as standard HTTP parameters using {@link Ext#urlEncode Ext.Object.toQueryString}.</p>
92150      */
92151
92152     /**
92153      * @cfg {Number} timeout Timeout for form actions in seconds (default is 30 seconds).
92154      */
92155     timeout: 30,
92156
92157     /**
92158      * @cfg {Object} api (Optional) If specified, load and submit actions will be handled
92159      * with {@link Ext.form.action.DirectLoad} and {@link Ext.form.action.DirectLoad}.
92160      * Methods which have been imported by {@link Ext.direct.Manager} can be specified here to load and submit
92161      * forms.
92162      * Such as the following:<pre><code>
92163 api: {
92164     load: App.ss.MyProfile.load,
92165     submit: App.ss.MyProfile.submit
92166 }
92167 </code></pre>
92168      * <p>Load actions can use <code>{@link #paramOrder}</code> or <code>{@link #paramsAsHash}</code>
92169      * to customize how the load method is invoked.
92170      * Submit actions will always use a standard form submit. The <tt>formHandler</tt> configuration must
92171      * be set on the associated server-side method which has been imported by {@link Ext.direct.Manager}.</p>
92172      */
92173
92174     /**
92175      * @cfg {Array/String} paramOrder <p>A list of params to be executed server side.
92176      * Defaults to <tt>undefined</tt>. Only used for the <code>{@link #api}</code>
92177      * <code>load</code> configuration.</p>
92178      * <p>Specify the params in the order in which they must be executed on the
92179      * server-side as either (1) an Array of String values, or (2) a String of params
92180      * delimited by either whitespace, comma, or pipe. For example,
92181      * any of the following would be acceptable:</p><pre><code>
92182 paramOrder: ['param1','param2','param3']
92183 paramOrder: 'param1 param2 param3'
92184 paramOrder: 'param1,param2,param3'
92185 paramOrder: 'param1|param2|param'
92186      </code></pre>
92187      */
92188
92189     /**
92190      * @cfg {Boolean} paramsAsHash Only used for the <code>{@link #api}</code>
92191      * <code>load</code> configuration. If <tt>true</tt>, parameters will be sent as a
92192      * single hash collection of named arguments (defaults to <tt>false</tt>). Providing a
92193      * <tt>{@link #paramOrder}</tt> nullifies this configuration.
92194      */
92195     paramsAsHash: false,
92196
92197     /**
92198      * @cfg {String} waitTitle
92199      * The default title to show for the waiting message box (defaults to <tt>'Please Wait...'</tt>)
92200      */
92201     waitTitle: 'Please Wait...',
92202
92203     /**
92204      * @cfg {Boolean} trackResetOnLoad If set to <tt>true</tt>, {@link #reset}() resets to the last loaded
92205      * or {@link #setValues}() data instead of when the form was first created.  Defaults to <tt>false</tt>.
92206      */
92207     trackResetOnLoad: false,
92208
92209     /**
92210      * @cfg {Boolean} standardSubmit
92211      * <p>If set to <tt>true</tt>, a standard HTML form submit is used instead
92212      * of a XHR (Ajax) style form submission. Defaults to <tt>false</tt>. All of
92213      * the field values, plus any additional params configured via {@link #baseParams}
92214      * and/or the <code>options</code> to {@link #submit}, will be included in the
92215      * values submitted in the form.</p>
92216      */
92217
92218     /**
92219      * @cfg {Mixed} waitMsgTarget
92220      * By default wait messages are displayed with Ext.MessageBox.wait. You can target a specific
92221      * element by passing it or its id or mask the form itself by passing in true. Defaults to <tt>undefined</tt>.
92222      */
92223
92224
92225     // Private
92226     wasDirty: false,
92227
92228
92229     /**
92230      * Destroys this object.
92231      */
92232     destroy: function() {
92233         this.clearListeners();
92234     },
92235
92236     /**
92237      * @private
92238      * Handle addition or removal of descendant items. Invalidates the cached list of fields
92239      * so that {@link #getFields} will do a fresh query next time it is called. Also adds listeners
92240      * for state change events on added fields, and tracks components with formBind=true.
92241      */
92242     onItemAddOrRemove: function(parent, child) {
92243         var me = this,
92244             isAdding = !!child.ownerCt,
92245             isContainer = child.isContainer;
92246
92247         function handleField(field) {
92248             // Listen for state change events on fields
92249             me[isAdding ? 'mon' : 'mun'](field, {
92250                 validitychange: me.checkValidity,
92251                 dirtychange: me.checkDirty,
92252                 scope: me,
92253                 buffer: 100 //batch up sequential calls to avoid excessive full-form validation
92254             });
92255             // Flush the cached list of fields
92256             delete me._fields;
92257         }
92258
92259         if (child.isFormField) {
92260             handleField(child);
92261         }
92262         else if (isContainer) {
92263             // Walk down
92264             Ext.Array.forEach(child.query('[isFormField]'), handleField);
92265         }
92266
92267         // Flush the cached list of formBind components
92268         delete this._boundItems;
92269
92270         // Check form bind, but only after initial add
92271         if (me.initialized) {
92272             me.onValidityChange(!me.hasInvalidField());
92273         }
92274     },
92275
92276     /**
92277      * Return all the {@link Ext.form.field.Field} components in the owner container.
92278      * @return {Ext.util.MixedCollection} Collection of the Field objects
92279      */
92280     getFields: function() {
92281         var fields = this._fields;
92282         if (!fields) {
92283             fields = this._fields = Ext.create('Ext.util.MixedCollection');
92284             fields.addAll(this.owner.query('[isFormField]'));
92285         }
92286         return fields;
92287     },
92288
92289     getBoundItems: function() {
92290         var boundItems = this._boundItems;
92291         if (!boundItems) {
92292             boundItems = this._boundItems = Ext.create('Ext.util.MixedCollection');
92293             boundItems.addAll(this.owner.query('[formBind]'));
92294         }
92295         return boundItems;
92296     },
92297
92298     /**
92299      * Returns true if the form contains any invalid fields. No fields will be marked as invalid
92300      * as a result of calling this; to trigger marking of fields use {@link #isValid} instead.
92301      */
92302     hasInvalidField: function() {
92303         return !!this.getFields().findBy(function(field) {
92304             var preventMark = field.preventMark,
92305                 isValid;
92306             field.preventMark = true;
92307             isValid = field.isValid();
92308             field.preventMark = preventMark;
92309             return !isValid;
92310         });
92311     },
92312
92313     /**
92314      * Returns true if client-side validation on the form is successful. Any invalid fields will be
92315      * marked as invalid. If you only want to determine overall form validity without marking anything,
92316      * use {@link #hasInvalidField} instead.
92317      * @return Boolean
92318      */
92319     isValid: function() {
92320         var me = this,
92321             invalid;
92322         me.batchLayouts(function() {
92323             invalid = me.getFields().filterBy(function(field) {
92324                 return !field.validate();
92325             });
92326         });
92327         return invalid.length < 1;
92328     },
92329
92330     /**
92331      * Check whether the validity of the entire form has changed since it was last checked, and
92332      * if so fire the {@link #validitychange validitychange} event. This is automatically invoked
92333      * when an individual field's validity changes.
92334      */
92335     checkValidity: function() {
92336         var me = this,
92337             valid = !me.hasInvalidField();
92338         if (valid !== me.wasValid) {
92339             me.onValidityChange(valid);
92340             me.fireEvent('validitychange', me, valid);
92341             me.wasValid = valid;
92342         }
92343     },
92344
92345     /**
92346      * @private
92347      * Handle changes in the form's validity. If there are any sub components with
92348      * formBind=true then they are enabled/disabled based on the new validity.
92349      * @param {Boolean} valid
92350      */
92351     onValidityChange: function(valid) {
92352         var boundItems = this.getBoundItems();
92353         if (boundItems) {
92354             boundItems.each(function(cmp) {
92355                 if (cmp.disabled === valid) {
92356                     cmp.setDisabled(!valid);
92357                 }
92358             });
92359         }
92360     },
92361
92362     /**
92363      * <p>Returns true if any fields in this form have changed from their original values.</p>
92364      * <p>Note that if this BasicForm was configured with {@link #trackResetOnLoad} then the
92365      * Fields' <em>original values</em> are updated when the values are loaded by {@link #setValues}
92366      * or {@link #loadRecord}.</p>
92367      * @return Boolean
92368      */
92369     isDirty: function() {
92370         return !!this.getFields().findBy(function(f) {
92371             return f.isDirty();
92372         });
92373     },
92374
92375     /**
92376      * Check whether the dirty state of the entire form has changed since it was last checked, and
92377      * if so fire the {@link #dirtychange dirtychange} event. This is automatically invoked
92378      * when an individual field's dirty state changes.
92379      */
92380     checkDirty: function() {
92381         var dirty = this.isDirty();
92382         if (dirty !== this.wasDirty) {
92383             this.fireEvent('dirtychange', this, dirty);
92384             this.wasDirty = dirty;
92385         }
92386     },
92387
92388     /**
92389      * <p>Returns true if the form contains a file upload field. This is used to determine the
92390      * method for submitting the form: File uploads are not performed using normal 'Ajax' techniques,
92391      * that is they are <b>not</b> performed using XMLHttpRequests. Instead a hidden <tt>&lt;form></tt>
92392      * element containing all the fields is created temporarily and submitted with its
92393      * <a href="http://www.w3.org/TR/REC-html40/present/frames.html#adef-target">target</a> set to refer
92394      * to a dynamically generated, hidden <tt>&lt;iframe></tt> which is inserted into the document
92395      * but removed after the return data has been gathered.</p>
92396      * <p>The server response is parsed by the browser to create the document for the IFRAME. If the
92397      * server is using JSON to send the return object, then the
92398      * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17">Content-Type</a> header
92399      * must be set to "text/html" in order to tell the browser to insert the text unchanged into the document body.</p>
92400      * <p>Characters which are significant to an HTML parser must be sent as HTML entities, so encode
92401      * "&lt;" as "&amp;lt;", "&amp;" as "&amp;amp;" etc.</p>
92402      * <p>The response text is retrieved from the document, and a fake XMLHttpRequest object
92403      * is created containing a <tt>responseText</tt> property in order to conform to the
92404      * requirements of event handlers and callbacks.</p>
92405      * <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>
92406      * and some server technologies (notably JEE) may require some custom processing in order to
92407      * retrieve parameter names and parameter values from the packet content.</p>
92408      * @return Boolean
92409      */
92410     hasUpload: function() {
92411         return !!this.getFields().findBy(function(f) {
92412             return f.isFileUpload();
92413         });
92414     },
92415
92416     /**
92417      * Performs a predefined action (an implementation of {@link Ext.form.action.Action})
92418      * to perform application-specific processing.
92419      * @param {String/Ext.form.action.Action} action The name of the predefined action type,
92420      * or instance of {@link Ext.form.action.Action} to perform.
92421      * @param {Object} options (optional) The options to pass to the {@link Ext.form.action.Action}
92422      * that will get created, if the <tt>action</tt> argument is a String.
92423      * <p>All of the config options listed below are supported by both the
92424      * {@link Ext.form.action.Submit submit} and {@link Ext.form.action.Load load}
92425      * actions unless otherwise noted (custom actions could also accept
92426      * other config options):</p><ul>
92427      *
92428      * <li><b>url</b> : String<div class="sub-desc">The url for the action (defaults
92429      * to the form's {@link #url}.)</div></li>
92430      *
92431      * <li><b>method</b> : String<div class="sub-desc">The form method to use (defaults
92432      * to the form's method, or POST if not defined)</div></li>
92433      *
92434      * <li><b>params</b> : String/Object<div class="sub-desc"><p>The params to pass
92435      * (defaults to the form's baseParams, or none if not defined)</p>
92436      * <p>Parameters are encoded as standard HTTP parameters using {@link Ext#urlEncode Ext.Object.toQueryString}.</p></div></li>
92437      *
92438      * <li><b>headers</b> : Object<div class="sub-desc">Request headers to set for the action.</div></li>
92439      *
92440      * <li><b>success</b> : Function<div class="sub-desc">The callback that will
92441      * be invoked after a successful response (see top of
92442      * {@link Ext.form.action.Submit submit} and {@link Ext.form.action.Load load}
92443      * for a description of what constitutes a successful response).
92444      * The function is passed the following parameters:<ul>
92445      * <li><tt>form</tt> : The {@link Ext.form.Basic} that requested the action.</li>
92446      * <li><tt>action</tt> : The {@link Ext.form.action.Action Action} object which performed the operation.
92447      * <div class="sub-desc">The action object contains these properties of interest:<ul>
92448      * <li><tt>{@link Ext.form.action.Action#response response}</tt></li>
92449      * <li><tt>{@link Ext.form.action.Action#result result}</tt> : interrogate for custom postprocessing</li>
92450      * <li><tt>{@link Ext.form.action.Action#type type}</tt></li>
92451      * </ul></div></li></ul></div></li>
92452      *
92453      * <li><b>failure</b> : Function<div class="sub-desc">The callback that will be invoked after a
92454      * failed transaction attempt. The function is passed the following parameters:<ul>
92455      * <li><tt>form</tt> : The {@link Ext.form.Basic} that requested the action.</li>
92456      * <li><tt>action</tt> : The {@link Ext.form.action.Action Action} object which performed the operation.
92457      * <div class="sub-desc">The action object contains these properties of interest:<ul>
92458      * <li><tt>{@link Ext.form.action.Action#failureType failureType}</tt></li>
92459      * <li><tt>{@link Ext.form.action.Action#response response}</tt></li>
92460      * <li><tt>{@link Ext.form.action.Action#result result}</tt> : interrogate for custom postprocessing</li>
92461      * <li><tt>{@link Ext.form.action.Action#type type}</tt></li>
92462      * </ul></div></li></ul></div></li>
92463      *
92464      * <li><b>scope</b> : Object<div class="sub-desc">The scope in which to call the
92465      * callback functions (The <tt>this</tt> reference for the callback functions).</div></li>
92466      *
92467      * <li><b>clientValidation</b> : Boolean<div class="sub-desc">Submit Action only.
92468      * Determines whether a Form's fields are validated in a final call to
92469      * {@link Ext.form.Basic#isValid isValid} prior to submission. Set to <tt>false</tt>
92470      * to prevent this. If undefined, pre-submission field validation is performed.</div></li></ul>
92471      *
92472      * @return {Ext.form.Basic} this
92473      */
92474     doAction: function(action, options) {
92475         if (Ext.isString(action)) {
92476             action = Ext.ClassManager.instantiateByAlias('formaction.' + action, Ext.apply({}, options, {form: this}));
92477         }
92478         if (this.fireEvent('beforeaction', this, action) !== false) {
92479             this.beforeAction(action);
92480             Ext.defer(action.run, 100, action);
92481         }
92482         return this;
92483     },
92484
92485     /**
92486      * Shortcut to {@link #doAction do} a {@link Ext.form.action.Submit submit action}. This will use the
92487      * {@link Ext.form.action.Submit AJAX submit action} by default. If the {@link #standardsubmit} config is
92488      * enabled it will use a standard form element to submit, or if the {@link #api} config is present it will
92489      * use the {@link Ext.form.action.DirectLoad Ext.direct.Direct submit action}.
92490      * @param {Object} options The options to pass to the action (see {@link #doAction} for details).<br>
92491      * <p>The following code:</p><pre><code>
92492 myFormPanel.getForm().submit({
92493     clientValidation: true,
92494     url: 'updateConsignment.php',
92495     params: {
92496         newStatus: 'delivered'
92497     },
92498     success: function(form, action) {
92499        Ext.Msg.alert('Success', action.result.msg);
92500     },
92501     failure: function(form, action) {
92502         switch (action.failureType) {
92503             case Ext.form.action.Action.CLIENT_INVALID:
92504                 Ext.Msg.alert('Failure', 'Form fields may not be submitted with invalid values');
92505                 break;
92506             case Ext.form.action.Action.CONNECT_FAILURE:
92507                 Ext.Msg.alert('Failure', 'Ajax communication failed');
92508                 break;
92509             case Ext.form.action.Action.SERVER_INVALID:
92510                Ext.Msg.alert('Failure', action.result.msg);
92511        }
92512     }
92513 });
92514 </code></pre>
92515      * would process the following server response for a successful submission:<pre><code>
92516 {
92517     "success":true, // note this is Boolean, not string
92518     "msg":"Consignment updated"
92519 }
92520 </code></pre>
92521      * and the following server response for a failed submission:<pre><code>
92522 {
92523     "success":false, // note this is Boolean, not string
92524     "msg":"You do not have permission to perform this operation"
92525 }
92526 </code></pre>
92527      * @return {Ext.form.Basic} this
92528      */
92529     submit: function(options) {
92530         return this.doAction(this.standardSubmit ? 'standardsubmit' : this.api ? 'directsubmit' : 'submit', options);
92531     },
92532
92533     /**
92534      * Shortcut to {@link #doAction do} a {@link Ext.form.action.Load load action}.
92535      * @param {Object} options The options to pass to the action (see {@link #doAction} for details)
92536      * @return {Ext.form.Basic} this
92537      */
92538     load: function(options) {
92539         return this.doAction(this.api ? 'directload' : 'load', options);
92540     },
92541
92542     /**
92543      * Persists the values in this form into the passed {@link Ext.data.Model} object in a beginEdit/endEdit block.
92544      * @param {Ext.data.Record} record The record to edit
92545      * @return {Ext.form.Basic} this
92546      */
92547     updateRecord: function(record) {
92548         var fields = record.fields,
92549             values = this.getFieldValues(),
92550             name,
92551             obj = {};
92552
92553         fields.each(function(f) {
92554             name = f.name;
92555             if (name in values) {
92556                 obj[name] = values[name];
92557             }
92558         });
92559
92560         record.beginEdit();
92561         record.set(obj);
92562         record.endEdit();
92563
92564         return this;
92565     },
92566
92567     /**
92568      * Loads an {@link Ext.data.Model} into this form by calling {@link #setValues} with the
92569      * {@link Ext.data.Model#data record data}.
92570      * See also {@link #trackResetOnLoad}.
92571      * @param {Ext.data.Model} record The record to load
92572      * @return {Ext.form.Basic} this
92573      */
92574     loadRecord: function(record) {
92575         this._record = record;
92576         return this.setValues(record.data);
92577     },
92578     
92579     /**
92580      * Returns the last Ext.data.Model instance that was loaded via {@link #loadRecord}
92581      * @return {Ext.data.Model} The record
92582      */
92583     getRecord: function() {
92584         return this._record;
92585     },
92586
92587     /**
92588      * @private
92589      * Called before an action is performed via {@link #doAction}.
92590      * @param {Ext.form.action.Action} action The Action instance that was invoked
92591      */
92592     beforeAction: function(action) {
92593         var waitMsg = action.waitMsg,
92594             maskCls = Ext.baseCSSPrefix + 'mask-loading',
92595             waitMsgTarget;
92596
92597         // Call HtmlEditor's syncValue before actions
92598         this.getFields().each(function(f) {
92599             if (f.isFormField && f.syncValue) {
92600                 f.syncValue();
92601             }
92602         });
92603
92604         if (waitMsg) {
92605             waitMsgTarget = this.waitMsgTarget;
92606             if (waitMsgTarget === true) {
92607                 this.owner.el.mask(waitMsg, maskCls);
92608             } else if (waitMsgTarget) {
92609                 waitMsgTarget = this.waitMsgTarget = Ext.get(waitMsgTarget);
92610                 waitMsgTarget.mask(waitMsg, maskCls);
92611             } else {
92612                 Ext.MessageBox.wait(waitMsg, action.waitTitle || this.waitTitle);
92613             }
92614         }
92615     },
92616
92617     /**
92618      * @private
92619      * Called after an action is performed via {@link #doAction}.
92620      * @param {Ext.form.action.Action} action The Action instance that was invoked
92621      * @param {Boolean} success True if the action completed successfully, false, otherwise.
92622      */
92623     afterAction: function(action, success) {
92624         if (action.waitMsg) {
92625             var MessageBox = Ext.MessageBox,
92626                 waitMsgTarget = this.waitMsgTarget;
92627             if (waitMsgTarget === true) {
92628                 this.owner.el.unmask();
92629             } else if (waitMsgTarget) {
92630                 waitMsgTarget.unmask();
92631             } else {
92632                 MessageBox.updateProgress(1);
92633                 MessageBox.hide();
92634             }
92635         }
92636         if (success) {
92637             if (action.reset) {
92638                 this.reset();
92639             }
92640             Ext.callback(action.success, action.scope || action, [this, action]);
92641             this.fireEvent('actioncomplete', this, action);
92642         } else {
92643             Ext.callback(action.failure, action.scope || action, [this, action]);
92644             this.fireEvent('actionfailed', this, action);
92645         }
92646     },
92647
92648
92649     /**
92650      * Find a specific {@link Ext.form.field.Field} in this form by id or name.
92651      * @param {String} id The value to search for (specify either a {@link Ext.Component#id id} or
92652      * {@link Ext.form.field.Field#getName name or hiddenName}).
92653      * @return Ext.form.field.Field The first matching field, or <tt>null</tt> if none was found.
92654      */
92655     findField: function(id) {
92656         return this.getFields().findBy(function(f) {
92657             return f.id === id || f.getName() === id;
92658         });
92659     },
92660
92661
92662     /**
92663      * Mark fields in this form invalid in bulk.
92664      * @param {Array/Object} errors Either an array in the form <code>[{id:'fieldId', msg:'The message'}, ...]</code>,
92665      * an object hash of <code>{id: msg, id2: msg2}</code>, or a {@link Ext.data.Errors} object.
92666      * @return {Ext.form.Basic} this
92667      */
92668     markInvalid: function(errors) {
92669         var me = this;
92670
92671         function mark(fieldId, msg) {
92672             var field = me.findField(fieldId);
92673             if (field) {
92674                 field.markInvalid(msg);
92675             }
92676         }
92677
92678         if (Ext.isArray(errors)) {
92679             Ext.each(errors, function(err) {
92680                 mark(err.id, err.msg);
92681             });
92682         }
92683         else if (errors instanceof Ext.data.Errors) {
92684             errors.each(function(err) {
92685                 mark(err.field, err.message);
92686             });
92687         }
92688         else {
92689             Ext.iterate(errors, mark);
92690         }
92691         return this;
92692     },
92693
92694     /**
92695      * Set values for fields in this form in bulk.
92696      * @param {Array/Object} values Either an array in the form:<pre><code>
92697 [{id:'clientName', value:'Fred. Olsen Lines'},
92698  {id:'portOfLoading', value:'FXT'},
92699  {id:'portOfDischarge', value:'OSL'} ]</code></pre>
92700      * or an object hash of the form:<pre><code>
92701 {
92702     clientName: 'Fred. Olsen Lines',
92703     portOfLoading: 'FXT',
92704     portOfDischarge: 'OSL'
92705 }</code></pre>
92706      * @return {Ext.form.Basic} this
92707      */
92708     setValues: function(values) {
92709         var me = this;
92710
92711         function setVal(fieldId, val) {
92712             var field = me.findField(fieldId);
92713             if (field) {
92714                 field.setValue(val);
92715                 if (me.trackResetOnLoad) {
92716                     field.resetOriginalValue();
92717                 }
92718             }
92719         }
92720
92721         if (Ext.isArray(values)) {
92722             // array of objects
92723             Ext.each(values, function(val) {
92724                 setVal(val.id, val.value);
92725             });
92726         } else {
92727             // object hash
92728             Ext.iterate(values, setVal);
92729         }
92730         return this;
92731     },
92732
92733     /**
92734      * Retrieves the fields in the form as a set of key/value pairs, using their
92735      * {@link Ext.form.field.Field#getSubmitData getSubmitData()} method to collect the values.
92736      * If multiple fields return values under the same name those values will be combined into an Array.
92737      * This is similar to {@link #getFieldValues} except that this method collects only String values for
92738      * submission, while getFieldValues collects type-specific data values (e.g. Date objects for date fields.)
92739      * @param {Boolean} asString (optional) If true, will return the key/value collection as a single
92740      * URL-encoded param string. Defaults to false.
92741      * @param {Boolean} dirtyOnly (optional) If true, only fields that are dirty will be included in the result.
92742      * Defaults to false.
92743      * @param {Boolean} includeEmptyText (optional) If true, the configured emptyText of empty fields will be used.
92744      * Defaults to false.
92745      * @return {String/Object}
92746      */
92747     getValues: function(asString, dirtyOnly, includeEmptyText, useDataValues) {
92748         var values = {};
92749
92750         this.getFields().each(function(field) {
92751             if (!dirtyOnly || field.isDirty()) {
92752                 var data = field[useDataValues ? 'getModelData' : 'getSubmitData'](includeEmptyText);
92753                 if (Ext.isObject(data)) {
92754                     Ext.iterate(data, function(name, val) {
92755                         if (includeEmptyText && val === '') {
92756                             val = field.emptyText || '';
92757                         }
92758                         if (name in values) {
92759                             var bucket = values[name],
92760                                 isArray = Ext.isArray;
92761                             if (!isArray(bucket)) {
92762                                 bucket = values[name] = [bucket];
92763                             }
92764                             if (isArray(val)) {
92765                                 values[name] = bucket.concat(val);
92766                             } else {
92767                                 bucket.push(val);
92768                             }
92769                         } else {
92770                             values[name] = val;
92771                         }
92772                     });
92773                 }
92774             }
92775         });
92776
92777         if (asString) {
92778             values = Ext.Object.toQueryString(values);
92779         }
92780         return values;
92781     },
92782
92783     /**
92784      * Retrieves the fields in the form as a set of key/value pairs, using their
92785      * {@link Ext.form.field.Field#getModelData getModelData()} method to collect the values.
92786      * If multiple fields return values under the same name those values will be combined into an Array.
92787      * This is similar to {@link #getValues} except that this method collects type-specific data values
92788      * (e.g. Date objects for date fields) while getValues returns only String values for submission.
92789      * @param {Boolean} dirtyOnly (optional) If true, only fields that are dirty will be included in the result.
92790      * Defaults to false.
92791      * @return {Object}
92792      */
92793     getFieldValues: function(dirtyOnly) {
92794         return this.getValues(false, dirtyOnly, false, true);
92795     },
92796
92797     /**
92798      * Clears all invalid field messages in this form.
92799      * @return {Ext.form.Basic} this
92800      */
92801     clearInvalid: function() {
92802         var me = this;
92803         me.batchLayouts(function() {
92804             me.getFields().each(function(f) {
92805                 f.clearInvalid();
92806             });
92807         });
92808         return me;
92809     },
92810
92811     /**
92812      * Resets all fields in this form.
92813      * @return {Ext.form.Basic} this
92814      */
92815     reset: function() {
92816         var me = this;
92817         me.batchLayouts(function() {
92818             me.getFields().each(function(f) {
92819                 f.reset();
92820             });
92821         });
92822         return me;
92823     },
92824
92825     /**
92826      * Calls {@link Ext#apply Ext.apply} for all fields in this form with the passed object.
92827      * @param {Object} obj The object to be applied
92828      * @return {Ext.form.Basic} this
92829      */
92830     applyToFields: function(obj) {
92831         this.getFields().each(function(f) {
92832             Ext.apply(f, obj);
92833         });
92834         return this;
92835     },
92836
92837     /**
92838      * Calls {@link Ext#applyIf Ext.applyIf} for all field in this form with the passed object.
92839      * @param {Object} obj The object to be applied
92840      * @return {Ext.form.Basic} this
92841      */
92842     applyIfToFields: function(obj) {
92843         this.getFields().each(function(f) {
92844             Ext.applyIf(f, obj);
92845         });
92846         return this;
92847     },
92848
92849     /**
92850      * @private
92851      * Utility wrapper that suspends layouts of all field parent containers for the duration of a given
92852      * function. Used during full-form validation and resets to prevent huge numbers of layouts.
92853      * @param {Function} fn
92854      */
92855     batchLayouts: function(fn) {
92856         var me = this,
92857             suspended = new Ext.util.HashMap();
92858
92859         // Temporarily suspend layout on each field's immediate owner so we don't get a huge layout cascade
92860         me.getFields().each(function(field) {
92861             var ownerCt = field.ownerCt;
92862             if (!suspended.contains(ownerCt)) {
92863                 suspended.add(ownerCt);
92864                 ownerCt.oldSuspendLayout = ownerCt.suspendLayout;
92865                 ownerCt.suspendLayout = true;
92866             }
92867         });
92868
92869         // Invoke the function
92870         fn();
92871
92872         // Un-suspend the container layouts
92873         suspended.each(function(id, ct) {
92874             ct.suspendLayout = ct.oldSuspendLayout;
92875             delete ct.oldSuspendLayout;
92876         });
92877
92878         // Trigger a single layout
92879         me.owner.doComponentLayout();
92880     }
92881 });
92882
92883 /**
92884  * @class Ext.form.FieldAncestor
92885
92886 A mixin for {@link Ext.container.Container} components that are likely to have form fields in their
92887 items subtree. Adds the following capabilities:
92888
92889 - Methods for handling the addition and removal of {@link Ext.form.Labelable} and {@link Ext.form.field.Field}
92890   instances at any depth within the container.
92891 - Events ({@link #fieldvaliditychange} and {@link #fielderrorchange}) for handling changes to the state
92892   of individual fields at the container level.
92893 - Automatic application of {@link #fieldDefaults} config properties to each field added within the
92894   container, to facilitate uniform configuration of all fields.
92895
92896 This mixin is primarily for internal use by {@link Ext.form.Panel} and {@link Ext.form.FieldContainer},
92897 and should not normally need to be used directly.
92898
92899  * @markdown
92900  * @docauthor Jason Johnston <jason@sencha.com>
92901  */
92902 Ext.define('Ext.form.FieldAncestor', {
92903
92904     /**
92905      * @cfg {Object} fieldDefaults
92906      * <p>If specified, the properties in this object are used as default config values for each
92907      * {@link Ext.form.Labelable} instance (e.g. {@link Ext.form.field.Base} or {@link Ext.form.FieldContainer})
92908      * that is added as a descendant of this container. Corresponding values specified in an individual field's
92909      * own configuration, or from the {@link Ext.container.Container#defaults defaults config} of its parent container,
92910      * will take precedence. See the documentation for {@link Ext.form.Labelable} to see what config
92911      * options may be specified in the <tt>fieldDefaults</tt>.</p>
92912      * <p>Example:</p>
92913      * <pre><code>new Ext.form.Panel({
92914     fieldDefaults: {
92915         labelAlign: 'left',
92916         labelWidth: 100
92917     },
92918     items: [{
92919         xtype: 'fieldset',
92920         defaults: {
92921             labelAlign: 'top'
92922         },
92923         items: [{
92924             name: 'field1'
92925         }, {
92926             name: 'field2'
92927         }]
92928     }, {
92929         xtype: 'fieldset',
92930         items: [{
92931             name: 'field3',
92932             labelWidth: 150
92933         }, {
92934             name: 'field4'
92935         }]
92936     }]
92937 });</code></pre>
92938      * <p>In this example, field1 and field2 will get labelAlign:'top' (from the fieldset's <tt>defaults</tt>)
92939      * and labelWidth:100 (from <tt>fieldDefaults</tt>), field3 and field4 will both get labelAlign:'left' (from
92940      * <tt>fieldDefaults</tt> and field3 will use the labelWidth:150 from its own config.</p>
92941      */
92942
92943
92944     /**
92945      * @protected Initializes the FieldAncestor's state; this must be called from the initComponent method
92946      * of any components importing this mixin.
92947      */
92948     initFieldAncestor: function() {
92949         var me = this,
92950             onSubtreeChange = me.onFieldAncestorSubtreeChange;
92951
92952         me.addEvents(
92953             /**
92954              * @event fielderrorchange
92955              * Fires when the validity state of any one of the {@link Ext.form.field.Field} instances within this
92956              * container changes.
92957              * @param {Ext.form.FieldAncestor} this
92958              * @param {Ext.form.Labelable} The Field instance whose validity changed
92959              * @param {String} isValid The field's new validity state
92960              */
92961             'fieldvaliditychange',
92962
92963             /**
92964              * @event fielderrorchange
92965              * Fires when the active error message is changed for any one of the {@link Ext.form.Labelable}
92966              * instances within this container.
92967              * @param {Ext.form.FieldAncestor} this
92968              * @param {Ext.form.Labelable} The Labelable instance whose active error was changed
92969              * @param {String} error The active error message
92970              */
92971             'fielderrorchange'
92972         );
92973
92974         // Catch addition and removal of descendant fields
92975         me.on('add', onSubtreeChange, me);
92976         me.on('remove', onSubtreeChange, me);
92977
92978         me.initFieldDefaults();
92979     },
92980
92981     /**
92982      * @private Initialize the {@link #fieldDefaults} object
92983      */
92984     initFieldDefaults: function() {
92985         if (!this.fieldDefaults) {
92986             this.fieldDefaults = {};
92987         }
92988     },
92989
92990     /**
92991      * @private
92992      * Handle the addition and removal of components in the FieldAncestor component's child tree.
92993      */
92994     onFieldAncestorSubtreeChange: function(parent, child) {
92995         var me = this,
92996             isAdding = !!child.ownerCt;
92997
92998         function handleCmp(cmp) {
92999             var isLabelable = cmp.isFieldLabelable,
93000                 isField = cmp.isFormField;
93001             if (isLabelable || isField) {
93002                 if (isLabelable) {
93003                     me['onLabelable' + (isAdding ? 'Added' : 'Removed')](cmp);
93004                 }
93005                 if (isField) {
93006                     me['onField' + (isAdding ? 'Added' : 'Removed')](cmp);
93007                 }
93008             }
93009             else if (cmp.isContainer) {
93010                 Ext.Array.forEach(cmp.getRefItems(), handleCmp);
93011             }
93012         }
93013         handleCmp(child);
93014     },
93015
93016     /**
93017      * @protected Called when a {@link Ext.form.Labelable} instance is added to the container's subtree.
93018      * @param {Ext.form.Labelable} labelable The instance that was added
93019      */
93020     onLabelableAdded: function(labelable) {
93021         var me = this;
93022
93023         // buffer slightly to avoid excessive firing while sub-fields are changing en masse
93024         me.mon(labelable, 'errorchange', me.handleFieldErrorChange, me, {buffer: 10});
93025
93026         labelable.setFieldDefaults(me.fieldDefaults);
93027     },
93028
93029     /**
93030      * @protected Called when a {@link Ext.form.field.Field} instance is added to the container's subtree.
93031      * @param {Ext.form.field.Field} field The field which was added
93032      */
93033     onFieldAdded: function(field) {
93034         var me = this;
93035         me.mon(field, 'validitychange', me.handleFieldValidityChange, me);
93036     },
93037
93038     /**
93039      * @protected Called when a {@link Ext.form.Labelable} instance is removed from the container's subtree.
93040      * @param {Ext.form.Labelable} labelable The instance that was removed
93041      */
93042     onLabelableRemoved: function(labelable) {
93043         var me = this;
93044         me.mun(labelable, 'errorchange', me.handleFieldErrorChange, me);
93045     },
93046
93047     /**
93048      * @protected Called when a {@link Ext.form.field.Field} instance is removed from the container's subtree.
93049      * @param {Ext.form.field.Field} field The field which was removed
93050      */
93051     onFieldRemoved: function(field) {
93052         var me = this;
93053         me.mun(field, 'validitychange', me.handleFieldValidityChange, me);
93054     },
93055
93056     /**
93057      * @private Handle validitychange events on sub-fields; invoke the aggregated event and method
93058      */
93059     handleFieldValidityChange: function(field, isValid) {
93060         var me = this;
93061         me.fireEvent('fieldvaliditychange', me, field, isValid);
93062         me.onFieldValidityChange();
93063     },
93064
93065     /**
93066      * @private Handle errorchange events on sub-fields; invoke the aggregated event and method
93067      */
93068     handleFieldErrorChange: function(labelable, activeError) {
93069         var me = this;
93070         me.fireEvent('fielderrorchange', me, labelable, activeError);
93071         me.onFieldErrorChange();
93072     },
93073
93074     /**
93075      * @protected Fired when the validity of any field within the container changes.
93076      * @param {Ext.form.field.Field} The sub-field whose validity changed
93077      * @param {String} The new validity state
93078      */
93079     onFieldValidityChange: Ext.emptyFn,
93080
93081     /**
93082      * @protected Fired when the error message of any field within the container changes.
93083      * @param {Ext.form.Labelable} The sub-field whose active error changed
93084      * @param {String} The new active error message
93085      */
93086     onFieldErrorChange: Ext.emptyFn
93087
93088 });
93089 /**
93090  * @class Ext.layout.container.CheckboxGroup
93091  * @extends Ext.layout.container.Container
93092  * <p>This layout implements the column arrangement for {@link Ext.form.CheckboxGroup} and {@link Ext.form.RadioGroup}.
93093  * It groups the component's sub-items into columns based on the component's
93094  * {@link Ext.form.CheckboxGroup#columns columns} and {@link Ext.form.CheckboxGroup#vertical} config properties.</p>
93095  *
93096  */
93097 Ext.define('Ext.layout.container.CheckboxGroup', {
93098     extend: 'Ext.layout.container.Container',
93099     alias: ['layout.checkboxgroup'],
93100
93101
93102     onLayout: function() {
93103         var numCols = this.getColCount(),
93104             shadowCt = this.getShadowCt(),
93105             owner = this.owner,
93106             items = owner.items,
93107             shadowItems = shadowCt.items,
93108             numItems = items.length,
93109             colIndex = 0,
93110             i, numRows;
93111
93112         // Distribute the items into the appropriate column containers. We add directly to the
93113         // containers' items collection rather than calling container.add(), because we need the
93114         // checkboxes to maintain their original ownerCt. The distribution is done on each layout
93115         // in case items have been added, removed, or reordered.
93116
93117         shadowItems.each(function(col) {
93118             col.items.clear();
93119         });
93120
93121         // If columns="auto", then the number of required columns may change as checkboxes are added/removed
93122         // from the CheckboxGroup; adjust to match.
93123         while (shadowItems.length > numCols) {
93124             shadowCt.remove(shadowItems.last());
93125         }
93126         while (shadowItems.length < numCols) {
93127             shadowCt.add({
93128                 xtype: 'container',
93129                 cls: owner.groupCls,
93130                 flex: 1
93131             });
93132         }
93133
93134         if (owner.vertical) {
93135             numRows = Math.ceil(numItems / numCols);
93136             for (i = 0; i < numItems; i++) {
93137                 if (i > 0 && i % numRows === 0) {
93138                     colIndex++;
93139                 }
93140                 shadowItems.getAt(colIndex).items.add(items.getAt(i));
93141             }
93142         } else {
93143             for (i = 0; i < numItems; i++) {
93144                 colIndex = i % numCols;
93145                 shadowItems.getAt(colIndex).items.add(items.getAt(i));
93146             }
93147         }
93148
93149         if (!shadowCt.rendered) {
93150             shadowCt.render(this.getRenderTarget());
93151         } else {
93152             // Ensure all items are rendered in the correct place in the correct column - this won't
93153             // get done by the column containers themselves if their dimensions are not changing.
93154             shadowItems.each(function(col) {
93155                 var layout = col.getLayout();
93156                 layout.renderItems(layout.getLayoutItems(), layout.getRenderTarget());
93157             });
93158         }
93159
93160         shadowCt.doComponentLayout();
93161     },
93162
93163
93164     // We don't want to render any items to the owner directly, that gets handled by each column's own layout
93165     renderItems: Ext.emptyFn,
93166
93167
93168     /**
93169      * @private
93170      * Creates and returns the shadow hbox container that will be used to arrange the owner's items
93171      * into columns.
93172      */
93173     getShadowCt: function() {
93174         var me = this,
93175             shadowCt = me.shadowCt,
93176             owner, items, item, columns, columnsIsArray, numCols, i;
93177
93178         if (!shadowCt) {
93179             // Create the column containers based on the owner's 'columns' config
93180             owner = me.owner;
93181             columns = owner.columns;
93182             columnsIsArray = Ext.isArray(columns);
93183             numCols = me.getColCount();
93184             items = [];
93185             for(i = 0; i < numCols; i++) {
93186                 item = {
93187                     xtype: 'container',
93188                     cls: owner.groupCls
93189                 };
93190                 if (columnsIsArray) {
93191                     // Array can contain mixture of whole numbers, used as fixed pixel widths, and fractional
93192                     // numbers, used as relative flex values.
93193                     if (columns[i] < 1) {
93194                         item.flex = columns[i];
93195                     } else {
93196                         item.width = columns[i];
93197                     }
93198                 }
93199                 else {
93200                     // All columns the same width
93201                     item.flex = 1;
93202                 }
93203                 items.push(item);
93204             }
93205
93206             // Create the shadow container; delay rendering until after items are added to the columns
93207             shadowCt = me.shadowCt = Ext.createWidget('container', {
93208                 layout: 'hbox',
93209                 items: items,
93210                 ownerCt: owner
93211             });
93212         }
93213         
93214         return shadowCt;
93215     },
93216
93217
93218     /**
93219      * @private Get the number of columns in the checkbox group
93220      */
93221     getColCount: function() {
93222         var owner = this.owner,
93223             colsCfg = owner.columns;
93224         return Ext.isArray(colsCfg) ? colsCfg.length : (Ext.isNumber(colsCfg) ? colsCfg : owner.items.length);
93225     }
93226
93227 });
93228
93229 /**
93230  * @class Ext.form.FieldContainer
93231  * @extends Ext.container.Container
93232
93233 FieldContainer is a derivation of {@link Ext.container.Container Container} that implements the
93234 {@link Ext.form.Labelable Labelable} mixin. This allows it to be configured so that it is rendered with
93235 a {@link #fieldLabel field label} and optional {@link #msgTarget error message} around its sub-items.
93236 This is useful for arranging a group of fields or other components within a single item in a form, so
93237 that it lines up nicely with other fields. A common use is for grouping a set of related fields under
93238 a single label in a form.
93239
93240 The container's configured {@link #items} will be layed out within the field body area according to the
93241 configured {@link #layout} type. The default layout is `'autocontainer'`.
93242
93243 Like regular fields, FieldContainer can inherit its decoration configuration from the
93244 {@link Ext.form.Panel#fieldDefaults fieldDefaults} of an enclosing FormPanel. In addition,
93245 FieldContainer itself can pass {@link #fieldDefaults} to any {@link Ext.form.Labelable fields}
93246 it may itself contain.
93247
93248 If you are grouping a set of {@link Ext.form.field.Checkbox Checkbox} or {@link Ext.form.field.Radio Radio}
93249 fields in a single labeled container, consider using a {@link Ext.form.CheckboxGroup}
93250 or {@link Ext.form.RadioGroup} instead as they are specialized for handling those types.
93251 {@img Ext.form.FieldContainer/Ext.form.FieldContainer1.png Ext.form.FieldContainer component}
93252 __Example usage:__
93253
93254     Ext.create('Ext.form.Panel', {
93255         title: 'FieldContainer Example',
93256         width: 550,
93257         bodyPadding: 10,
93258     
93259         items: [{
93260             xtype: 'fieldcontainer',
93261             fieldLabel: 'Last Three Jobs',
93262             labelWidth: 100,
93263     
93264             // The body area will contain three text fields, arranged
93265             // horizontally, separated by draggable splitters.
93266             layout: 'hbox',
93267             items: [{
93268                 xtype: 'textfield',
93269                 flex: 1
93270             }, {
93271                 xtype: 'splitter'
93272             }, {
93273                 xtype: 'textfield',
93274                 flex: 1
93275             }, {
93276                 xtype: 'splitter'
93277             }, {
93278                 xtype: 'textfield',
93279                 flex: 1
93280             }]
93281         }],
93282         renderTo: Ext.getBody()
93283     });
93284
93285 __Usage of {@link #fieldDefaults}:__
93286 {@img Ext.form.FieldContainer/Ext.form.FieldContainer2.png Ext.form.FieldContainer component}
93287
93288     Ext.create('Ext.form.Panel', {
93289         title: 'FieldContainer Example',
93290         width: 350,
93291         bodyPadding: 10,
93292     
93293         items: [{
93294             xtype: 'fieldcontainer',
93295             fieldLabel: 'Your Name',
93296             labelWidth: 75,
93297             defaultType: 'textfield',
93298     
93299             // Arrange fields vertically, stretched to full width
93300             layout: 'anchor',
93301             defaults: {
93302                 layout: '100%'
93303             },
93304     
93305             // These config values will be applied to both sub-fields, except
93306             // for Last Name which will use its own msgTarget.
93307             fieldDefaults: {
93308                 msgTarget: 'under',
93309                 labelAlign: 'top'
93310             },
93311     
93312             items: [{
93313                 fieldLabel: 'First Name',
93314                 name: 'firstName'
93315             }, {
93316                 fieldLabel: 'Last Name',
93317                 name: 'lastName',
93318                 msgTarget: 'under'
93319             }]
93320         }],
93321         renderTo: Ext.getBody()
93322     });
93323
93324
93325  * @constructor
93326  * Creates a new Ext.form.FieldContainer instance.
93327  * @param {Object} config The component configuration.
93328  *
93329  * @xtype fieldcontainer
93330  * @markdown
93331  * @docauthor Jason Johnston <jason@sencha.com>
93332  */
93333 Ext.define('Ext.form.FieldContainer', {
93334     extend: 'Ext.container.Container',
93335     mixins: {
93336         labelable: 'Ext.form.Labelable',
93337         fieldAncestor: 'Ext.form.FieldAncestor'
93338     },
93339     alias: 'widget.fieldcontainer',
93340
93341     componentLayout: 'field',
93342
93343     /**
93344      * @cfg {Boolean} combineLabels
93345      * If set to true, and there is no defined {@link #fieldLabel}, the field container will automatically
93346      * generate its label by combining the labels of all the fields it contains. Defaults to false.
93347      */
93348     combineLabels: false,
93349
93350     /**
93351      * @cfg {String} labelConnector
93352      * The string to use when joining the labels of individual sub-fields, when {@link #combineLabels} is
93353      * set to true. Defaults to ', '.
93354      */
93355     labelConnector: ', ',
93356
93357     /**
93358      * @cfg {Boolean} combineErrors
93359      * If set to true, the field container will automatically combine and display the validation errors from
93360      * all the fields it contains as a single error on the container, according to the configured
93361      * {@link #msgTarget}. Defaults to false.
93362      */
93363     combineErrors: false,
93364     
93365     maskOnDisable: false,
93366
93367     initComponent: function() {
93368         var me = this,
93369             onSubCmpAddOrRemove = me.onSubCmpAddOrRemove;
93370
93371         // Init mixins
93372         me.initLabelable();
93373         me.initFieldAncestor();
93374
93375         me.callParent();
93376     },
93377
93378     /**
93379      * @protected Called when a {@link Ext.form.Labelable} instance is added to the container's subtree.
93380      * @param {Ext.form.Labelable} labelable The instance that was added
93381      */
93382     onLabelableAdded: function(labelable) {
93383         var me = this;
93384         me.mixins.fieldAncestor.onLabelableAdded.call(this, labelable);
93385         me.updateLabel();
93386     },
93387
93388     /**
93389      * @protected Called when a {@link Ext.form.Labelable} instance is removed from the container's subtree.
93390      * @param {Ext.form.Labelable} labelable The instance that was removed
93391      */
93392     onLabelableRemoved: function(labelable) {
93393         var me = this;
93394         me.mixins.fieldAncestor.onLabelableRemoved.call(this, labelable);
93395         me.updateLabel();
93396     },
93397
93398     onRender: function() {
93399         var me = this,
93400             renderSelectors = me.renderSelectors,
93401             applyIf = Ext.applyIf;
93402
93403         applyIf(renderSelectors, me.getLabelableSelectors());
93404
93405         me.callParent(arguments);
93406     },
93407
93408     initRenderTpl: function() {
93409         var me = this;
93410         if (!me.hasOwnProperty('renderTpl')) {
93411             me.renderTpl = me.getTpl('labelableRenderTpl');
93412         }
93413         return me.callParent();
93414     },
93415
93416     initRenderData: function() {
93417         return Ext.applyIf(this.callParent(), this.getLabelableRenderData());
93418     },
93419
93420     /**
93421      * Returns the combined field label if {@link #combineLabels} is set to true and if there is no
93422      * set {@link #fieldLabel}. Otherwise returns the fieldLabel like normal. You can also override
93423      * this method to provide a custom generated label.
93424      */
93425     getFieldLabel: function() {
93426         var label = this.fieldLabel || '';
93427         if (!label && this.combineLabels) {
93428             label = Ext.Array.map(this.query('[isFieldLabelable]'), function(field) {
93429                 return field.getFieldLabel();
93430             }).join(this.labelConnector);
93431         }
93432         return label;
93433     },
93434
93435     /**
93436      * @private Updates the content of the labelEl if it is rendered
93437      */
93438     updateLabel: function() {
93439         var me = this,
93440             label = me.labelEl;
93441         if (label) {
93442             label.update(me.getFieldLabel());
93443         }
93444     },
93445
93446
93447     /**
93448      * @private Fired when the error message of any field within the container changes, and updates the
93449      * combined error message to match.
93450      */
93451     onFieldErrorChange: function(field, activeError) {
93452         if (this.combineErrors) {
93453             var me = this,
93454                 oldError = me.getActiveError(),
93455                 invalidFields = Ext.Array.filter(me.query('[isFormField]'), function(field) {
93456                     return field.hasActiveError();
93457                 }),
93458                 newErrors = me.getCombinedErrors(invalidFields);
93459
93460             if (newErrors) {
93461                 me.setActiveErrors(newErrors);
93462             } else {
93463                 me.unsetActiveError();
93464             }
93465
93466             if (oldError !== me.getActiveError()) {
93467                 me.doComponentLayout();
93468             }
93469         }
93470     },
93471
93472     /**
93473      * Takes an Array of invalid {@link Ext.form.field.Field} objects and builds a combined list of error
93474      * messages from them. Defaults to prepending each message by the field name and a colon. This
93475      * can be overridden to provide custom combined error message handling, for instance changing
93476      * the format of each message or sorting the array (it is sorted in order of appearance by default).
93477      * @param {Array} invalidFields An Array of the sub-fields which are currently invalid.
93478      * @return {Array} The combined list of error messages
93479      */
93480     getCombinedErrors: function(invalidFields) {
93481         var forEach = Ext.Array.forEach,
93482             errors = [];
93483         forEach(invalidFields, function(field) {
93484             forEach(field.getActiveErrors(), function(error) {
93485                 var label = field.getFieldLabel();
93486                 errors.push((label ? label + ': ' : '') + error);
93487             });
93488         });
93489         return errors;
93490     },
93491
93492     getTargetEl: function() {
93493         return this.bodyEl || this.callParent();
93494     }
93495 });
93496
93497 /**
93498  * @class Ext.form.CheckboxGroup
93499  * @extends Ext.form.FieldContainer
93500  * <p>A {@link Ext.form.FieldContainer field container} which has a specialized layout for arranging
93501  * {@link Ext.form.field.Checkbox} controls into columns, and provides convenience {@link Ext.form.field.Field} methods
93502  * for {@link #getValue getting}, {@link #setValue setting}, and {@link #validate validating} the group
93503  * of checkboxes as a whole.</p>
93504  * <p><b>Validation:</b> Individual checkbox fields themselves have no default validation behavior, but
93505  * sometimes you want to require a user to select at least one of a group of checkboxes. CheckboxGroup
93506  * allows this by setting the config <tt>{@link #allowBlank}:false</tt>; when the user does not check at
93507  * least one of the checkboxes, the entire group will be highlighted as invalid and the
93508  * {@link #blankText error message} will be displayed according to the {@link #msgTarget} config.</p>
93509  * <p><b>Layout:</b> The default layout for CheckboxGroup makes it easy to arrange the checkboxes into
93510  * columns; see the {@link #columns} and {@link #vertical} config documentation for details. You may also
93511  * use a completely different layout by setting the {@link #layout} to one of the other supported layout
93512  * types; for instance you may wish to use a custom arrangement of hbox and vbox containers. In that case
93513  * the checkbox components at any depth will still be managed by the CheckboxGroup's validation.</p>
93514  * {@img Ext.form.RadioGroup/Ext.form.RadioGroup.png Ext.form.RadioGroup component}
93515  * <p>Example usage:</p>
93516  * <pre><code>
93517     Ext.create('Ext.form.Panel', {
93518         title: 'RadioGroup Example',
93519         width: 300,
93520         height: 125,
93521         bodyPadding: 10,
93522         renderTo: Ext.getBody(),        
93523         items:[{            
93524             xtype: 'radiogroup',
93525             fieldLabel: 'Two Columns',
93526             // Arrange radio buttons into two columns, distributed vertically
93527             columns: 2,
93528             vertical: true,
93529             items: [
93530                 {boxLabel: 'Item 1', name: 'rb', inputValue: '1'},
93531                 {boxLabel: 'Item 2', name: 'rb', inputValue: '2', checked: true},
93532                 {boxLabel: 'Item 3', name: 'rb', inputValue: '3'},
93533                 {boxLabel: 'Item 4', name: 'rb', inputValue: '4'},
93534                 {boxLabel: 'Item 5', name: 'rb', inputValue: '5'},
93535                 {boxLabel: 'Item 6', name: 'rb', inputValue: '6'}
93536             ]
93537         }]
93538     });
93539  * </code></pre>
93540  * @constructor
93541  * Creates a new CheckboxGroup
93542  * @param {Object} config Configuration options
93543  * @xtype checkboxgroup
93544  */
93545 Ext.define('Ext.form.CheckboxGroup', {
93546     extend:'Ext.form.FieldContainer',
93547     mixins: {
93548         field: 'Ext.form.field.Field'
93549     },
93550     alias: 'widget.checkboxgroup',
93551     requires: ['Ext.layout.container.CheckboxGroup', 'Ext.form.field.Base'],
93552
93553     /**
93554      * @cfg {String} name
93555      * @hide
93556      */
93557
93558     /**
93559      * @cfg {Array} items An Array of {@link Ext.form.field.Checkbox Checkbox}es or Checkbox config objects
93560      * to arrange in the group.
93561      */
93562
93563     /**
93564      * @cfg {String/Number/Array} columns Specifies the number of columns to use when displaying grouped
93565      * checkbox/radio controls using automatic layout.  This config can take several types of values:
93566      * <ul><li><b>'auto'</b> : <p class="sub-desc">The controls will be rendered one per column on one row and the width
93567      * of each column will be evenly distributed based on the width of the overall field container. This is the default.</p></li>
93568      * <li><b>Number</b> : <p class="sub-desc">If you specific a number (e.g., 3) that number of columns will be
93569      * created and the contained controls will be automatically distributed based on the value of {@link #vertical}.</p></li>
93570      * <li><b>Array</b> : <p class="sub-desc">You can also specify an array of column widths, mixing integer
93571      * (fixed width) and float (percentage width) values as needed (e.g., [100, .25, .75]). Any integer values will
93572      * be rendered first, then any float values will be calculated as a percentage of the remaining space. Float
93573      * values do not have to add up to 1 (100%) although if you want the controls to take up the entire field
93574      * container you should do so.</p></li></ul>
93575      */
93576     columns : 'auto',
93577
93578     /**
93579      * @cfg {Boolean} vertical True to distribute contained controls across columns, completely filling each column
93580      * top to bottom before starting on the next column.  The number of controls in each column will be automatically
93581      * calculated to keep columns as even as possible.  The default value is false, so that controls will be added
93582      * to columns one at a time, completely filling each row left to right before starting on the next row.
93583      */
93584     vertical : false,
93585
93586     /**
93587      * @cfg {Boolean} allowBlank False to validate that at least one item in the group is checked (defaults to true).
93588      * If no items are selected at validation time, {@link #blankText} will be used as the error text.
93589      */
93590     allowBlank : true,
93591
93592     /**
93593      * @cfg {String} blankText Error text to display if the {@link #allowBlank} validation fails (defaults to "You must
93594      * select at least one item in this group")
93595      */
93596     blankText : "You must select at least one item in this group",
93597
93598     // private
93599     defaultType : 'checkboxfield',
93600
93601     // private
93602     groupCls : Ext.baseCSSPrefix + 'form-check-group',
93603
93604     /**
93605      * @cfg {String} fieldBodyCls
93606      * An extra CSS class to be applied to the body content element in addition to {@link #baseBodyCls}.
93607      * Defaults to 'x-form-checkboxgroup-body'.
93608      */
93609     fieldBodyCls: Ext.baseCSSPrefix + 'form-checkboxgroup-body',
93610
93611     // private
93612     layout: 'checkboxgroup',
93613
93614     initComponent: function() {
93615         var me = this;
93616         me.callParent();
93617         me.initField();
93618     },
93619
93620     /**
93621      * @protected
93622      * Initializes the field's value based on the initial config. If the {@link #value} config is specified
93623      * then we use that to set the value; otherwise we initialize the originalValue by querying the values of
93624      * all sub-checkboxes after they have been initialized.
93625      */
93626     initValue: function() {
93627         var me = this,
93628             valueCfg = me.value;
93629         me.originalValue = me.lastValue = valueCfg || me.getValue();
93630         if (valueCfg) {
93631             me.setValue(valueCfg);
93632         }
93633     },
93634
93635     /**
93636      * @protected
93637      * When a checkbox is added to the group, monitor it for changes
93638      */
93639     onFieldAdded: function(field) {
93640         var me = this;
93641         if (field.isCheckbox) {
93642             me.mon(field, 'change', me.checkChange, me);
93643         }
93644         me.callParent(arguments);
93645     },
93646
93647     onFieldRemoved: function(field) {
93648         var me = this;
93649         if (field.isCheckbox) {
93650             me.mun(field, 'change', me.checkChange, me);
93651         }
93652         me.callParent(arguments);
93653     },
93654
93655     // private override - the group value is a complex object, compare using object serialization
93656     isEqual: function(value1, value2) {
93657         var toQueryString = Ext.Object.toQueryString;
93658         return toQueryString(value1) === toQueryString(value2);
93659     },
93660
93661     /**
93662      * Runs CheckboxGroup's validations and returns an array of any errors. The only error by default
93663      * is if allowBlank is set to true and no items are checked.
93664      * @return {Array} Array of all validation errors
93665      */
93666     getErrors: function() {
93667         var errors = [];
93668         if (!this.allowBlank && Ext.isEmpty(this.getChecked())) {
93669             errors.push(this.blankText);
93670         }
93671         return errors;
93672     },
93673
93674     /**
93675      * @private Returns all checkbox components within the container
93676      */
93677     getBoxes: function() {
93678         return this.query('[isCheckbox]');
93679     },
93680
93681     /**
93682      * @private Convenience function which calls the given function for every checkbox in the group
93683      * @param {Function} fn The function to call
93684      * @param {Object} scope Optional scope object
93685      */
93686     eachBox: function(fn, scope) {
93687         Ext.Array.forEach(this.getBoxes(), fn, scope || this);
93688     },
93689
93690     /**
93691      * Returns an Array of all checkboxes in the container which are currently checked
93692      * @return {Array} Array of Ext.form.field.Checkbox components
93693      */
93694     getChecked: function() {
93695         return Ext.Array.filter(this.getBoxes(), function(cb) {
93696             return cb.getValue();
93697         });
93698     },
93699
93700     // private override
93701     isDirty: function(){
93702         return Ext.Array.some(this.getBoxes(), function(cb) {
93703             return cb.isDirty();
93704         });
93705     },
93706
93707     // private override
93708     setReadOnly: function(readOnly) {
93709         this.eachBox(function(cb) {
93710             cb.setReadOnly(readOnly);
93711         });
93712         this.readOnly = readOnly;
93713     },
93714
93715     /**
93716      * Resets the checked state of all {@link Ext.form.field.Checkbox checkboxes} in the group to their
93717      * originally loaded values and clears any validation messages.
93718      * See {@link Ext.form.Basic}.{@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad}
93719      */
93720     reset: function() {
93721         var me = this,
93722             hadError = me.hasActiveError(),
93723             preventMark = me.preventMark;
93724         me.preventMark = true;
93725         me.batchChanges(function() {
93726             me.eachBox(function(cb) {
93727                 cb.reset();
93728             });
93729         });
93730         me.preventMark = preventMark;
93731         me.unsetActiveError();
93732         if (hadError) {
93733             me.doComponentLayout();
93734         }
93735     },
93736
93737     // private override
93738     resetOriginalValue: function() {
93739         // Defer resetting of originalValue until after all sub-checkboxes have been reset so we get
93740         // the correct data from getValue()
93741         Ext.defer(function() {
93742             this.callParent();
93743         }, 1, this);
93744     },
93745
93746
93747     /**
93748      * <p>Sets the value(s) of all checkboxes in the group. The expected format is an Object of
93749      * name-value pairs corresponding to the names of the checkboxes in the group. Each pair can
93750      * have either a single or multiple values:</p>
93751      * <ul>
93752      *   <li>A single Boolean or String value will be passed to the <code>setValue</code> method of the
93753      *   checkbox with that name. See the rules in {@link Ext.form.field.Checkbox#setValue} for accepted values.</li>
93754      *   <li>An Array of String values will be matched against the {@link Ext.form.field.Checkbox#inputValue inputValue}
93755      *   of checkboxes in the group with that name; those checkboxes whose inputValue exists in the array will be
93756      *   checked and others will be unchecked.</li>
93757      * </ul>
93758      * <p>If a checkbox's name is not in the mapping at all, it will be unchecked.</p>
93759      * <p>An example:</p>
93760      * <pre><code>var myCheckboxGroup = new Ext.form.CheckboxGroup({
93761     columns: 3,
93762     items: [{
93763         name: 'cb1',
93764         boxLabel: 'Single 1'
93765     }, {
93766         name: 'cb2',
93767         boxLabel: 'Single 2'
93768     }, {
93769         name: 'cb3',
93770         boxLabel: 'Single 3'
93771     }, {
93772         name: 'cbGroup',
93773         boxLabel: 'Grouped 1'
93774         inputValue: 'value1'
93775     }, {
93776         name: 'cbGroup',
93777         boxLabel: 'Grouped 2'
93778         inputValue: 'value2'
93779     }, {
93780         name: 'cbGroup',
93781         boxLabel: 'Grouped 3'
93782         inputValue: 'value3'
93783     }]
93784 });
93785
93786 myCheckboxGroup.setValue({
93787     cb1: true,
93788     cb3: false,
93789     cbGroup: ['value1', 'value3']
93790 });</code></pre>
93791      * <p>The above code will cause the checkbox named 'cb1' to be checked, as well as the first and third
93792      * checkboxes named 'cbGroup'. The other three checkboxes will be unchecked.</p>
93793      * @param {Object} value The mapping of checkbox names to values.
93794      * @return {Ext.form.CheckboxGroup} this
93795      */
93796     setValue: function(value) {
93797         var me = this;
93798         me.batchChanges(function() {
93799             me.eachBox(function(cb) {
93800                 var name = cb.getName(),
93801                     cbValue = false;
93802                 if (value && name in value) {
93803                     if (Ext.isArray(value[name])) {
93804                         cbValue = Ext.Array.contains(value[name], cb.inputValue);
93805                     } else {
93806                         // single value, let the checkbox's own setValue handle conversion
93807                         cbValue = value[name];
93808                     }
93809                 }
93810                 cb.setValue(cbValue);
93811             });
93812         });
93813         return me;
93814     },
93815
93816
93817     /**
93818      * <p>Returns an object containing the values of all checked checkboxes within the group. Each key-value pair
93819      * in the object corresponds to a checkbox {@link Ext.form.field.Checkbox#name name}. If there is only one checked
93820      * checkbox with a particular name, the value of that pair will be the String
93821      * {@link Ext.form.field.Checkbox#inputValue inputValue} of that checkbox. If there are multiple checked checkboxes
93822      * with that name, the value of that pair will be an Array of the selected inputValues.</p>
93823      * <p>The object format returned from this method can also be passed directly to the {@link #setValue} method.</p>
93824      * <p>NOTE: In Ext 3, this method returned an array of Checkbox components; this was changed to make it more
93825      * consistent with other field components and with the {@link #setValue} argument signature. If you need the old
93826      * behavior in Ext 4+, use the {@link #getChecked} method instead.</p>
93827      */
93828     getValue: function() {
93829         var values = {};
93830         this.eachBox(function(cb) {
93831             var name = cb.getName(),
93832                 inputValue = cb.inputValue,
93833                 bucket;
93834             if (cb.getValue()) {
93835                 if (name in values) {
93836                     bucket = values[name];
93837                     if (!Ext.isArray(bucket)) {
93838                         bucket = values[name] = [bucket];
93839                     }
93840                     bucket.push(inputValue);
93841                 } else {
93842                     values[name] = inputValue;
93843                 }
93844             }
93845         });
93846         return values;
93847     },
93848
93849     /*
93850      * Don't return any data for submit; the form will get the info from the individual checkboxes themselves.
93851      */
93852     getSubmitData: function() {
93853         return null;
93854     },
93855
93856     /*
93857      * Don't return any data for the model; the form will get the info from the individual checkboxes themselves.
93858      */
93859     getModelData: function() {
93860         return null;
93861     },
93862
93863     validate: function() {
93864         var me = this,
93865             errors = me.getErrors(),
93866             isValid = Ext.isEmpty(errors),
93867             wasValid = !me.hasActiveError();
93868
93869         if (isValid) {
93870             me.unsetActiveError();
93871         } else {
93872             me.setActiveError(errors);
93873         }
93874         if (isValid !== wasValid) {
93875             me.fireEvent('validitychange', me, isValid);
93876             me.doComponentLayout();
93877         }
93878
93879         return isValid;
93880     }
93881
93882 }, function() {
93883
93884     this.borrow(Ext.form.field.Base, ['markInvalid', 'clearInvalid']);
93885
93886 });
93887
93888
93889 /**
93890  * @private
93891  * Private utility class for managing all {@link Ext.form.field.Checkbox} fields grouped by name.
93892  */
93893 Ext.define('Ext.form.CheckboxManager', {
93894     extend: 'Ext.util.MixedCollection',
93895     singleton: true,
93896
93897     getByName: function(name) {
93898         return this.filterBy(function(item) {
93899             return item.name == name;
93900         });
93901     },
93902
93903     getWithValue: function(name, value) {
93904         return this.filterBy(function(item) {
93905             return item.name == name && item.inputValue == value;
93906         });
93907     },
93908
93909     getChecked: function(name) {
93910         return this.filterBy(function(item) {
93911             return item.name == name && item.checked;
93912         });
93913     }
93914 });
93915
93916 /**
93917  * @class Ext.form.FieldSet
93918  * @extends Ext.container.Container
93919  * 
93920  * A container for grouping sets of fields, rendered as a HTML `fieldset` element. The {@link #title}
93921  * config will be rendered as the fieldset's `legend`.
93922  * 
93923  * While FieldSets commonly contain simple groups of fields, they are general {@link Ext.container.Container Containers}
93924  * and may therefore contain any type of components in their {@link #items}, including other nested containers.
93925  * The default {@link #layout} for the FieldSet's items is `'anchor'`, but it can be configured to use any other
93926  * layout type.
93927  * 
93928  * FieldSets may also be collapsed if configured to do so; this can be done in two ways:
93929  * 
93930  * 1. Set the {@link #collapsible} config to true; this will result in a collapse button being rendered next to
93931  *    the {@link #title legend title}, or:
93932  * 2. Set the {@link #checkboxToggle} config to true; this is similar to using {@link #collapsible} but renders
93933  *    a {@link Ext.form.field.Checkbox checkbox} in place of the toggle button. The fieldset will be expanded when the
93934  *    checkbox is checked and collapsed when it is unchecked. The checkbox will also be included in the
93935  *    {@link Ext.form.Basic#submit form submit parameters} using the {@link #checkboxName} as its parameter name.
93936  *
93937  * {@img Ext.form.FieldSet/Ext.form.FieldSet.png Ext.form.FieldSet component}
93938  *
93939  * ## Example usage
93940  * 
93941  *     Ext.create('Ext.form.Panel', {
93942  *         title: 'Simple Form with FieldSets',
93943  *         labelWidth: 75, // label settings here cascade unless overridden
93944  *         url: 'save-form.php',
93945  *         frame: true,
93946  *         bodyStyle: 'padding:5px 5px 0',
93947  *         width: 550,
93948  *         renderTo: Ext.getBody(),
93949  *         layout: 'column', // arrange fieldsets side by side
93950  *         defaults: {
93951  *             bodyPadding: 4
93952  *         },
93953  *         items: [{
93954  *             // Fieldset in Column 1 - collapsible via toggle button
93955  *             xtype:'fieldset',
93956  *             columnWidth: 0.5,
93957  *             title: 'Fieldset 1',
93958  *             collapsible: true,
93959  *             defaultType: 'textfield',
93960  *             defaults: {anchor: '100%'},
93961  *             layout: 'anchor',
93962  *             items :[{
93963  *                 fieldLabel: 'Field 1',
93964  *                 name: 'field1'
93965  *             }, {
93966  *                 fieldLabel: 'Field 2',
93967  *                 name: 'field2'
93968  *             }]
93969  *         }, {
93970  *             // Fieldset in Column 2 - collapsible via checkbox, collapsed by default, contains a panel
93971  *             xtype:'fieldset',
93972  *             title: 'Show Panel', // title or checkboxToggle creates fieldset header
93973  *             columnWidth: 0.5,
93974  *             checkboxToggle: true,
93975  *             collapsed: true, // fieldset initially collapsed
93976  *             layout:'anchor',
93977  *             items :[{
93978  *                 xtype: 'panel',
93979  *                 anchor: '100%',
93980  *                 title: 'Panel inside a fieldset',
93981  *                 frame: true,
93982  *                 height: 52
93983  *             }]
93984  *         }]
93985  *     });
93986  * 
93987  * @constructor
93988  * Create a new FieldSet
93989  * @param {Object} config Configuration options
93990  * @xtype fieldset
93991  * @docauthor Jason Johnston <jason@sencha.com>
93992  */
93993 Ext.define('Ext.form.FieldSet', {
93994     extend: 'Ext.container.Container',
93995     alias: 'widget.fieldset',
93996     uses: ['Ext.form.field.Checkbox', 'Ext.panel.Tool', 'Ext.layout.container.Anchor', 'Ext.layout.component.FieldSet'],
93997
93998     /**
93999      * @cfg {String} title
94000      * A title to be displayed in the fieldset's legend. May contain HTML markup.
94001      */
94002
94003     /**
94004      * @cfg {Boolean} checkboxToggle
94005      * Set to <tt>true</tt> to render a checkbox into the fieldset frame just
94006      * in front of the legend to expand/collapse the fieldset when the checkbox is toggled. (defaults
94007      * to <tt>false</tt>). This checkbox will be included in form submits using the {@link #checkboxName}.
94008      */
94009
94010     /**
94011      * @cfg {String} checkboxName
94012      * The name to assign to the fieldset's checkbox if <tt>{@link #checkboxToggle} = true</tt>
94013      * (defaults to <tt>'[fieldset id]-checkbox'</tt>).
94014      */
94015
94016     /**
94017      * @cfg {Boolean} collapsible
94018      * Set to <tt>true</tt> to make the fieldset collapsible and have the expand/collapse toggle button automatically
94019      * rendered into the legend element, <tt>false</tt> to keep the fieldset statically sized with no collapse
94020      * button (defaults to <tt>false</tt>). Another option is to configure <tt>{@link #checkboxToggle}</tt>.
94021      * Use the {@link #collapsed} config to collapse the fieldset by default.
94022      */
94023
94024     /**
94025      * @cfg {Boolean} collapsed
94026      * Set to <tt>true</tt> to render the fieldset as collapsed by default. If {@link #checkboxToggle} is specified,
94027      * the checkbox will also be unchecked by default.
94028      */
94029     collapsed: false,
94030
94031     /**
94032      * @property legend
94033      * @type Ext.Component
94034      * The component for the fieldset's legend. Will only be defined if the configuration requires a legend
94035      * to be created, by setting the {@link #title} or {@link #checkboxToggle} options.
94036      */
94037
94038     /**
94039      * @cfg {String} baseCls The base CSS class applied to the fieldset (defaults to <tt>'x-fieldset'</tt>).
94040      */
94041     baseCls: Ext.baseCSSPrefix + 'fieldset',
94042
94043     /**
94044      * @cfg {String} layout The {@link Ext.container.Container#layout} for the fieldset's immediate child items.
94045      * Defaults to <tt>'anchor'</tt>.
94046      */
94047     layout: 'anchor',
94048
94049     componentLayout: 'fieldset',
94050
94051     // No aria role necessary as fieldset has its own recognized semantics
94052     ariaRole: '',
94053
94054     renderTpl: ['<div class="{baseCls}-body"></div>'],
94055     
94056     maskOnDisable: false,
94057
94058     getElConfig: function(){
94059         return {tag: 'fieldset', id: this.id};
94060     },
94061
94062     initComponent: function() {
94063         var me = this,
94064             baseCls = me.baseCls;
94065
94066         me.callParent();
94067
94068         // Create the Legend component if needed
94069         me.initLegend();
94070
94071         // Add body el selector
94072         Ext.applyIf(me.renderSelectors, {
94073             body: '.' + baseCls + '-body'
94074         });
94075
94076         if (me.collapsed) {
94077             me.addCls(baseCls + '-collapsed');
94078             me.collapse();
94079         }
94080     },
94081
94082     // private
94083     onRender: function(container, position) {
94084         this.callParent(arguments);
94085         // Make sure the legend is created and rendered
94086         this.initLegend();
94087     },
94088
94089     /**
94090      * @private
94091      * Initialize and render the legend component if necessary
94092      */
94093     initLegend: function() {
94094         var me = this,
94095             legendItems,
94096             legend = me.legend;
94097
94098         // Create the legend component if needed and it hasn't been already
94099         if (!legend && (me.title || me.checkboxToggle || me.collapsible)) {
94100             legendItems = [];
94101
94102             // Checkbox
94103             if (me.checkboxToggle) {
94104                 legendItems.push(me.createCheckboxCmp());
94105             }
94106             // Toggle button
94107             else if (me.collapsible) {
94108                 legendItems.push(me.createToggleCmp());
94109             }
94110
94111             // Title
94112             legendItems.push(me.createTitleCmp());
94113
94114             legend = me.legend = Ext.create('Ext.container.Container', {
94115                 baseCls: me.baseCls + '-header',
94116                 ariaRole: '',
94117                 getElConfig: function(){
94118                     return {tag: 'legend', cls: this.baseCls};
94119                 },
94120                 items: legendItems
94121             });
94122         }
94123
94124         // Make sure legend is rendered if the fieldset is rendered
94125         if (legend && !legend.rendered && me.rendered) {
94126             me.legend.render(me.el, me.body); //insert before body element
94127         }
94128     },
94129
94130     /**
94131      * @protected
94132      * Creates the legend title component. This is only called internally, but could be overridden in subclasses
94133      * to customize the title component.
94134      * @return Ext.Component
94135      */
94136     createTitleCmp: function() {
94137         var me = this;
94138         me.titleCmp = Ext.create('Ext.Component', {
94139             html: me.title,
94140             cls: me.baseCls + '-header-text'
94141         });
94142         return me.titleCmp;
94143         
94144     },
94145
94146     /**
94147      * @property checkboxCmp
94148      * @type Ext.form.field.Checkbox
94149      * Refers to the {@link Ext.form.field.Checkbox} component that is added next to the title in the legend. Only
94150      * populated if the fieldset is configured with <tt>{@link #checkboxToggle}:true</tt>.
94151      */
94152
94153     /**
94154      * @protected
94155      * Creates the checkbox component. This is only called internally, but could be overridden in subclasses
94156      * to customize the checkbox's configuration or even return an entirely different component type.
94157      * @return Ext.Component
94158      */
94159     createCheckboxCmp: function() {
94160         var me = this,
94161             suffix = '-checkbox';
94162             
94163         me.checkboxCmp = Ext.create('Ext.form.field.Checkbox', {
94164             name: me.checkboxName || me.id + suffix,
94165             cls: me.baseCls + '-header' + suffix,
94166             checked: !me.collapsed,
94167             listeners: {
94168                 change: me.onCheckChange,
94169                 scope: me
94170             }
94171         });
94172         return me.checkboxCmp;
94173     },
94174
94175     /**
94176      * @property toggleCmp
94177      * @type Ext.panel.Tool
94178      * Refers to the {@link Ext.panel.Tool} component that is added as the collapse/expand button next
94179      * to the title in the legend. Only populated if the fieldset is configured with <tt>{@link #collapsible}:true</tt>.
94180      */
94181
94182     /**
94183      * @protected
94184      * Creates the toggle button component. This is only called internally, but could be overridden in
94185      * subclasses to customize the toggle component.
94186      * @return Ext.Component
94187      */
94188     createToggleCmp: function() {
94189         var me = this;
94190         me.toggleCmp = Ext.create('Ext.panel.Tool', {
94191             type: 'toggle',
94192             handler: me.toggle,
94193             scope: me
94194         });
94195         return me.toggleCmp;
94196     },
94197     
94198     /**
94199      * Sets the title of this fieldset
94200      * @param {String} title The new title
94201      * @return {Ext.form.FieldSet} this
94202      */
94203     setTitle: function(title) {
94204         var me = this;
94205         me.title = title;
94206         me.initLegend();
94207         me.titleCmp.update(title);
94208         return me;
94209     },
94210     
94211     getTargetEl : function() {
94212         return this.body || this.frameBody || this.el;
94213     },
94214     
94215     getContentTarget: function() {
94216         return this.body;
94217     },
94218     
94219     /**
94220      * @private
94221      * Include the legend component in the items for ComponentQuery
94222      */
94223     getRefItems: function(deep) {
94224         var refItems = this.callParent(arguments),
94225             legend = this.legend;
94226
94227         // Prepend legend items to ensure correct order
94228         if (legend) {
94229             refItems.unshift(legend);
94230             if (deep) {
94231                 refItems.unshift.apply(refItems, legend.getRefItems(true));
94232             }
94233         }
94234         return refItems;
94235     },
94236
94237     /**
94238      * Expands the fieldset.
94239      * @return {Ext.form.FieldSet} this
94240      */
94241     expand : function(){
94242         return this.setExpanded(true);
94243     },
94244     
94245     /**
94246      * Collapses the fieldset.
94247      * @return {Ext.form.FieldSet} this
94248      */
94249     collapse : function() {
94250         return this.setExpanded(false);
94251     },
94252
94253     /**
94254      * @private Collapse or expand the fieldset
94255      */
94256     setExpanded: function(expanded) {
94257         var me = this,
94258             checkboxCmp = me.checkboxCmp,
94259             toggleCmp = me.toggleCmp;
94260
94261         expanded = !!expanded;
94262         
94263         if (checkboxCmp) {
94264             checkboxCmp.setValue(expanded);
94265         }
94266         
94267         if (expanded) {
94268             me.removeCls(me.baseCls + '-collapsed');
94269         } else {
94270             me.addCls(me.baseCls + '-collapsed');
94271         }
94272         me.collapsed = !expanded;
94273         me.doComponentLayout();
94274         return me;
94275     },
94276
94277     /**
94278      * Toggle the fieldset's collapsed state to the opposite of what it is currently
94279      */
94280     toggle: function() {
94281         this.setExpanded(!!this.collapsed);
94282     },
94283
94284     /**
94285      * @private Handle changes in the checkbox checked state
94286      */
94287     onCheckChange: function(cmp, checked) {
94288         this.setExpanded(checked);
94289     },
94290
94291     beforeDestroy : function() {
94292         var legend = this.legend;
94293         if (legend) {
94294             legend.destroy();
94295         }
94296         this.callParent();
94297     }
94298 });
94299
94300 /**
94301  * @class Ext.form.Label
94302  * @extends Ext.Component
94303
94304 Produces a standalone `<label />` element which can be inserted into a form and be associated with a field
94305 in that form using the {@link #forId} property.
94306
94307 **NOTE:** in most cases it will be more appropriate to use the {@link Ext.form.Labelable#fieldLabel fieldLabel}
94308 and associated config properties ({@link Ext.form.Labelable#labelAlign}, {@link Ext.form.Labelable#labelWidth},
94309 etc.) in field components themselves, as that allows labels to be uniformly sized throughout the form.
94310 Ext.form.Label should only be used when your layout can not be achieved with the standard
94311 {@link Ext.form.Labelable field layout}.
94312
94313 You will likely be associating the label with a field component that extends {@link Ext.form.field.Base}, so
94314 you should make sure the {@link #forId} is set to the same value as the {@link Ext.form.field.Base#inputId inputId}
94315 of that field.
94316
94317 The label's text can be set using either the {@link #text} or {@link #html} configuration properties; the
94318 difference between the two is that the former will automatically escape HTML characters when rendering, while
94319 the latter will not.
94320 {@img Ext.form.Label/Ext.form.Label.png Ext.form.Label component}
94321 #Example usage:#
94322
94323 This example creates a Label after its associated Text field, an arrangement that cannot currently
94324 be achieved using the standard Field layout's labelAlign.
94325
94326     Ext.create('Ext.form.Panel', {
94327         title: 'Field with Label',
94328         width: 400,
94329         bodyPadding: 10,
94330         renderTo: Ext.getBody(),
94331         layout: {
94332             type: 'hbox',
94333             align: 'middle'
94334         },
94335         items: [{
94336             xtype: 'textfield',
94337             hideLabel: true,
94338             flex: 1
94339         }, {
94340             xtype: 'label',
94341             forId: 'myFieldId',
94342             text: 'My Awesome Field',
94343             margins: '0 0 0 10'
94344         }]
94345     });
94346
94347  * @constructor
94348  * Creates a new Label component.
94349  * @param {Ext.core.Element/String/Object} config The configuration options.
94350  * 
94351  * @xtype label
94352  * @markdown
94353  * @docauthor Jason Johnston <jason@sencha.com>
94354  */
94355 Ext.define('Ext.form.Label', {
94356     extend:'Ext.Component',
94357     alias: 'widget.label',
94358     requires: ['Ext.util.Format'],
94359
94360     /**
94361      * @cfg {String} text The plain text to display within the label (defaults to ''). If you need to include HTML
94362      * tags within the label's innerHTML, use the {@link #html} config instead.
94363      */
94364     /**
94365      * @cfg {String} forId The id of the input element to which this label will be bound via the standard HTML 'for'
94366      * attribute. If not specified, the attribute will not be added to the label. In most cases you will be
94367      * associating the label with a {@link Ext.form.field.Base} component, so you should make sure this matches
94368      * the {@link Ext.form.field.Base#inputId inputId} of that field.
94369      */
94370     /**
94371      * @cfg {String} html An HTML fragment that will be used as the label's innerHTML (defaults to '').
94372      * Note that if {@link #text} is specified it will take precedence and this value will be ignored.
94373      */
94374     
94375     maskOnDisable: false,
94376     getElConfig: function(){
94377         var me = this;
94378         return {
94379             tag: 'label', 
94380             id: me.id, 
94381             htmlFor: me.forId || '',
94382             html: me.text ? Ext.util.Format.htmlEncode(me.text) : (me.html || '') 
94383         };
94384     },
94385
94386     /**
94387      * Updates the label's innerHTML with the specified string.
94388      * @param {String} text The new label text
94389      * @param {Boolean} encode (optional) False to skip HTML-encoding the text when rendering it
94390      * to the label (defaults to true which encodes the value). This might be useful if you want to include
94391      * tags in the label's innerHTML rather than rendering them as string literals per the default logic.
94392      * @return {Label} this
94393      */
94394     setText : function(text, encode){
94395         var me = this;
94396         
94397         encode = encode !== false;
94398         if(encode) {
94399             me.text = text;
94400             delete me.html;
94401         } else {
94402             me.html = text;
94403             delete me.text;
94404         }
94405         
94406         if(me.rendered){
94407             me.el.dom.innerHTML = encode !== false ? Ext.util.Format.htmlEncode(text) : text;
94408         }
94409         return this;
94410     }
94411 });
94412
94413
94414 /**
94415  * @class Ext.form.Panel
94416  * @extends Ext.panel.Panel
94417
94418 FormPanel provides a standard container for forms. It is essentially a standard {@link Ext.panel.Panel} which
94419 automatically creates a {@link Ext.form.Basic BasicForm} for managing any {@link Ext.form.field.Field}
94420 objects that are added as descendants of the panel. It also includes conveniences for configuring and
94421 working with the BasicForm and the collection of Fields.
94422
94423 __Layout__
94424
94425 By default, FormPanel is configured with `{@link Ext.layout.container.Anchor layout:'anchor'}` for
94426 the layout of its immediate child items. This can be changed to any of the supported container layouts.
94427 The layout of sub-containers is configured in {@link Ext.container.Container#layout the standard way}.
94428
94429 __BasicForm__
94430
94431 Although **not listed** as configuration options of FormPanel, the FormPanel class accepts all
94432 of the config options supported by the {@link Ext.form.Basic} class, and will pass them along to
94433 the internal BasicForm when it is created.
94434
94435 **Note**: If subclassing FormPanel, any configuration options for the BasicForm must be applied to
94436 the `initialConfig` property of the FormPanel. Applying {@link Ext.form.Basic BasicForm}
94437 configuration settings to `this` will *not* affect the BasicForm's configuration.
94438
94439 The following events fired by the BasicForm will be re-fired by the FormPanel and can therefore be
94440 listened for on the FormPanel itself:
94441
94442 - {@link Ext.form.Basic#beforeaction beforeaction}
94443 - {@link Ext.form.Basic#actionfailed actionfailed}
94444 - {@link Ext.form.Basic#actioncomplete actioncomplete}
94445 - {@link Ext.form.Basic#validitychange validitychange}
94446 - {@link Ext.form.Basic#dirtychange dirtychange}
94447
94448 __Field Defaults__
94449
94450 The {@link #fieldDefaults} config option conveniently allows centralized configuration of default values
94451 for all fields added as descendants of the FormPanel. Any config option recognized by implementations
94452 of {@link Ext.form.Labelable} may be included in this object. See the {@link #fieldDefaults} documentation
94453 for details of how the defaults are applied.
94454
94455 __Form Validation__
94456
94457 With the default configuration, form fields are validated on-the-fly while the user edits their values.
94458 This can be controlled on a per-field basis (or via the {@link #fieldDefaults} config) with the field
94459 config properties {@link Ext.form.field.Field#validateOnChange} and {@link Ext.form.field.Base#checkChangeEvents},
94460 and the FormPanel's config properties {@link #pollForChanges} and {@link #pollInterval}.
94461
94462 Any component within the FormPanel can be configured with `formBind: true`. This will cause that
94463 component to be automatically disabled when the form is invalid, and enabled when it is valid. This is most
94464 commonly used for Button components to prevent submitting the form in an invalid state, but can be used on
94465 any component type.
94466
94467 For more information on form validation see the following:
94468
94469 - {@link Ext.form.field.Field#validateOnChange}
94470 - {@link #pollForChanges} and {@link #pollInterval}
94471 - {@link Ext.form.field.VTypes}
94472 - {@link Ext.form.Basic#doAction BasicForm.doAction clientValidation notes}
94473
94474 __Form Submission__
94475
94476 By default, Ext Forms are submitted through Ajax, using {@link Ext.form.action.Action}. See the documentation for
94477 {@link Ext.form.Basic} for details.
94478 {@img Ext.form.FormPanel/Ext.form.FormPanel.png Ext.form.FormPanel FormPanel component}
94479 __Example usage:__
94480
94481     Ext.create('Ext.form.Panel', {
94482         title: 'Simple Form',
94483         bodyPadding: 5,
94484         width: 350,
94485         
94486         // The form will submit an AJAX request to this URL when submitted
94487         url: 'save-form.php',
94488         
94489         // Fields will be arranged vertically, stretched to full width
94490         layout: 'anchor',
94491         defaults: {
94492             anchor: '100%'
94493         },
94494         
94495         // The fields
94496         defaultType: 'textfield',
94497         items: [{
94498             fieldLabel: 'First Name',
94499             name: 'first',
94500             allowBlank: false
94501         },{
94502             fieldLabel: 'Last Name',
94503             name: 'last',
94504             allowBlank: false
94505         }],
94506         
94507         // Reset and Submit buttons
94508         buttons: [{
94509             text: 'Reset',
94510             handler: function() {
94511                 this.up('form').getForm().reset();
94512             }
94513         }, {
94514             text: 'Submit',
94515             formBind: true, //only enabled once the form is valid
94516             disabled: true,
94517             handler: function() {
94518                 var form = this.up('form').getForm();
94519                 if (form.isValid()) {
94520                     form.submit({
94521                         success: function(form, action) {
94522                            Ext.Msg.alert('Success', action.result.msg);
94523                         },
94524                         failure: function(form, action) {
94525                             Ext.Msg.alert('Failed', action.result.msg);
94526                         }
94527                     });
94528                 }
94529             }
94530         }],
94531         renderTo: Ext.getBody()
94532     });
94533
94534  * @constructor
94535  * @param {Object} config Configuration options
94536  * @xtype form
94537  *
94538  * @markdown
94539  * @docauthor Jason Johnston <jason@sencha.com>
94540  */
94541 Ext.define('Ext.form.Panel', {
94542     extend:'Ext.panel.Panel',
94543     mixins: {
94544         fieldAncestor: 'Ext.form.FieldAncestor'
94545     },
94546     alias: 'widget.form',
94547     alternateClassName: ['Ext.FormPanel', 'Ext.form.FormPanel'],
94548     requires: ['Ext.form.Basic', 'Ext.util.TaskRunner'],
94549
94550     /**
94551      * @cfg {Boolean} pollForChanges
94552      * If set to <tt>true</tt>, sets up an interval task (using the {@link #pollInterval}) in which the 
94553      * panel's fields are repeatedly checked for changes in their values. This is in addition to the normal detection
94554      * each field does on its own input element, and is not needed in most cases. It does, however, provide a
94555      * means to absolutely guarantee detection of all changes including some edge cases in some browsers which
94556      * do not fire native events. Defaults to <tt>false</tt>.
94557      */
94558
94559     /**
94560      * @cfg {Number} pollInterval
94561      * Interval in milliseconds at which the form's fields are checked for value changes. Only used if
94562      * the {@link #pollForChanges} option is set to <tt>true</tt>. Defaults to 500 milliseconds.
94563      */
94564
94565     /**
94566      * @cfg {String} layout The {@link Ext.container.Container#layout} for the form panel's immediate child items.
94567      * Defaults to <tt>'anchor'</tt>.
94568      */
94569     layout: 'anchor',
94570
94571     ariaRole: 'form',
94572
94573     initComponent: function() {
94574         var me = this;
94575         
94576         if (me.frame) {
94577             me.border = false;
94578         }
94579         
94580         me.initFieldAncestor();
94581         me.callParent();
94582
94583         me.relayEvents(me.form, [
94584             'beforeaction',
94585             'actionfailed',
94586             'actioncomplete',
94587             'validitychange',
94588             'dirtychange'
94589         ]);
94590
94591         // Start polling if configured
94592         if (me.pollForChanges) {
94593             me.startPolling(me.pollInterval || 500);
94594         }
94595     },
94596
94597     initItems: function() {
94598         // Create the BasicForm
94599         var me = this;
94600         
94601         me.form = me.createForm();
94602         me.callParent();
94603         me.form.initialize();
94604     },
94605
94606     /**
94607      * @private
94608      */
94609     createForm: function() {
94610         return Ext.create('Ext.form.Basic', this, Ext.applyIf({listeners: {}}, this.initialConfig));
94611     },
94612
94613     /**
94614      * Provides access to the {@link Ext.form.Basic Form} which this Panel contains.
94615      * @return {Ext.form.Basic} The {@link Ext.form.Basic Form} which this Panel contains.
94616      */
94617     getForm: function() {
94618         return this.form;
94619     },
94620     
94621     /**
94622      * Loads an {@link Ext.data.Model} into this form (internally just calls {@link Ext.form.Basic#loadRecord})
94623      * See also {@link #trackResetOnLoad}.
94624      * @param {Ext.data.Model} record The record to load
94625      * @return {Ext.form.Basic} The Ext.form.Basic attached to this FormPanel
94626      */
94627     loadRecord: function(record) {
94628         return this.getForm().loadRecord(record);
94629     },
94630     
94631     /**
94632      * Returns the currently loaded Ext.data.Model instance if one was loaded via {@link #loadRecord}.
94633      * @return {Ext.data.Model} The loaded instance
94634      */
94635     getRecord: function() {
94636         return this.getForm().getRecord();
94637     },
94638     
94639     /**
94640      * Convenience function for fetching the current value of each field in the form. This is the same as calling
94641      * {@link Ext.form.Basic#getValues this.getForm().getValues()}
94642      * @return {Object} The current form field values, keyed by field name
94643      */
94644     getValues: function() {
94645         return this.getForm().getValues();
94646     },
94647
94648     beforeDestroy: function() {
94649         this.stopPolling();
94650         this.form.destroy();
94651         this.callParent();
94652     },
94653
94654     /**
94655      * This is a proxy for the underlying BasicForm's {@link Ext.form.Basic#load} call.
94656      * @param {Object} options The options to pass to the action (see {@link Ext.form.Basic#load} and
94657      * {@link Ext.form.Basic#doAction} for details)
94658      */
94659     load: function(options) {
94660         this.form.load(options);
94661     },
94662
94663     /**
94664      * This is a proxy for the underlying BasicForm's {@link Ext.form.Basic#submit} call.
94665      * @param {Object} options The options to pass to the action (see {@link Ext.form.Basic#submit} and
94666      * {@link Ext.form.Basic#doAction} for details)
94667      */
94668     submit: function(options) {
94669         this.form.submit(options);
94670     },
94671
94672     /*
94673      * Inherit docs, not using onDisable because it only gets fired
94674      * when the component is rendered.
94675      */
94676     disable: function(silent) {
94677         this.callParent(arguments);
94678         this.form.getFields().each(function(field) {
94679             field.disable();
94680         });
94681     },
94682
94683     /*
94684      * Inherit docs, not using onEnable because it only gets fired
94685      * when the component is rendered.
94686      */
94687     enable: function(silent) {
94688         this.callParent(arguments);
94689         this.form.getFields().each(function(field) {
94690             field.enable();
94691         });
94692     },
94693
94694     /**
94695      * Start an interval task to continuously poll all the fields in the form for changes in their
94696      * values. This is normally started automatically by setting the {@link #pollForChanges} config.
94697      * @param {Number} interval The interval in milliseconds at which the check should run.
94698      */
94699     startPolling: function(interval) {
94700         this.stopPolling();
94701         var task = Ext.create('Ext.util.TaskRunner', interval);
94702         task.start({
94703             interval: 0,
94704             run: this.checkChange,
94705             scope: this
94706         });
94707         this.pollTask = task;
94708     },
94709
94710     /**
94711      * Stop a running interval task that was started by {@link #startPolling}.
94712      */
94713     stopPolling: function() {
94714         var task = this.pollTask;
94715         if (task) {
94716             task.stopAll();
94717             delete this.pollTask;
94718         }
94719     },
94720
94721     /**
94722      * Forces each field within the form panel to 
94723      * {@link Ext.form.field.Field#checkChange check if its value has changed}.
94724      */
94725     checkChange: function() {
94726         this.form.getFields().each(function(field) {
94727             field.checkChange();
94728         });
94729     }
94730 });
94731
94732 /**
94733  * @class Ext.form.RadioGroup
94734  * @extends Ext.form.CheckboxGroup
94735  * <p>A {@link Ext.form.FieldContainer field container} which has a specialized layout for arranging
94736  * {@link Ext.form.field.Radio} controls into columns, and provides convenience {@link Ext.form.field.Field} methods
94737  * for {@link #getValue getting}, {@link #setValue setting}, and {@link #validate validating} the group
94738  * of radio buttons as a whole.</p>
94739  * <p><b>Validation:</b> Individual radio buttons themselves have no default validation behavior, but
94740  * sometimes you want to require a user to select one of a group of radios. RadioGroup
94741  * allows this by setting the config <tt>{@link #allowBlank}:false</tt>; when the user does not check at
94742  * one of the radio buttons, the entire group will be highlighted as invalid and the
94743  * {@link #blankText error message} will be displayed according to the {@link #msgTarget} config.</p>
94744  * <p><b>Layout:</b> The default layout for RadioGroup makes it easy to arrange the radio buttons into
94745  * columns; see the {@link #columns} and {@link #vertical} config documentation for details. You may also
94746  * use a completely different layout by setting the {@link #layout} to one of the other supported layout
94747  * types; for instance you may wish to use a custom arrangement of hbox and vbox containers. In that case
94748  * the Radio components at any depth will still be managed by the RadioGroup's validation.</p>
94749  * <p>Example usage:</p>
94750  * <pre><code>
94751 var myRadioGroup = new Ext.form.RadioGroup({
94752     id: 'myGroup',
94753     xtype: 'radiogroup',
94754     fieldLabel: 'Single Column',
94755     // Arrange radio buttons into three columns, distributed vertically
94756     columns: 3,
94757     vertical: true,
94758     items: [
94759         {boxLabel: 'Item 1', name: 'rb', inputValue: '1'},
94760         {boxLabel: 'Item 2', name: 'rb', inputValue: '2', checked: true},
94761         {boxLabel: 'Item 3', name: 'rb', inputValue: '3'}
94762         {boxLabel: 'Item 4', name: 'rb', inputValue: '4'}
94763         {boxLabel: 'Item 5', name: 'rb', inputValue: '5'}
94764         {boxLabel: 'Item 6', name: 'rb', inputValue: '6'}
94765     ]
94766 });
94767  * </code></pre>
94768  * @constructor
94769  * Creates a new RadioGroup
94770  * @param {Object} config Configuration options
94771  * @xtype radiogroup
94772  */
94773 Ext.define('Ext.form.RadioGroup', {
94774     extend: 'Ext.form.CheckboxGroup',
94775     alias: 'widget.radiogroup',
94776
94777     /**
94778      * @cfg {Array} items An Array of {@link Ext.form.field.Radio Radio}s or Radio config objects
94779      * to arrange in the group.
94780      */
94781     /**
94782      * @cfg {Boolean} allowBlank True to allow every item in the group to be blank (defaults to true).
94783      * If allowBlank = false and no items are selected at validation time, {@link @blankText} will
94784      * be used as the error text.
94785      */
94786     allowBlank : true,
94787     /**
94788      * @cfg {String} blankText Error text to display if the {@link #allowBlank} validation fails
94789      * (defaults to 'You must select one item in this group')
94790      */
94791     blankText : 'You must select one item in this group',
94792     
94793     // private
94794     defaultType : 'radiofield',
94795     
94796     // private
94797     groupCls : Ext.baseCSSPrefix + 'form-radio-group',
94798
94799     getBoxes: function() {
94800         return this.query('[isRadio]');
94801     }
94802
94803 });
94804
94805 /**
94806  * @private
94807  * Private utility class for managing all {@link Ext.form.field.Radio} fields grouped by name.
94808  */
94809 Ext.define('Ext.form.RadioManager', {
94810     extend: 'Ext.util.MixedCollection',
94811     singleton: true,
94812
94813     getByName: function(name) {
94814         return this.filterBy(function(item) {
94815             return item.name == name;
94816         });
94817     },
94818
94819     getWithValue: function(name, value) {
94820         return this.filterBy(function(item) {
94821             return item.name == name && item.inputValue == value;
94822         });
94823     },
94824
94825     getChecked: function(name) {
94826         return this.findBy(function(item) {
94827             return item.name == name && item.checked;
94828         });
94829     }
94830 });
94831
94832 /**
94833  * @class Ext.form.action.DirectLoad
94834  * @extends Ext.form.action.Load
94835  * <p>Provides {@link Ext.direct.Manager} support for loading form data.</p>
94836  * <p>This example illustrates usage of Ext.direct.Direct to <b>load</b> a form through Ext.Direct.</p>
94837  * <pre><code>
94838 var myFormPanel = new Ext.form.Panel({
94839     // configs for FormPanel
94840     title: 'Basic Information',
94841     renderTo: document.body,
94842     width: 300, height: 160,
94843     padding: 10,
94844
94845     // configs apply to child items
94846     defaults: {anchor: '100%'},
94847     defaultType: 'textfield',
94848     items: [{
94849         fieldLabel: 'Name',
94850         name: 'name'
94851     },{
94852         fieldLabel: 'Email',
94853         name: 'email'
94854     },{
94855         fieldLabel: 'Company',
94856         name: 'company'
94857     }],
94858
94859     // configs for BasicForm
94860     api: {
94861         // The server-side method to call for load() requests
94862         load: Profile.getBasicInfo,
94863         // The server-side must mark the submit handler as a 'formHandler'
94864         submit: Profile.updateBasicInfo
94865     },
94866     // specify the order for the passed params
94867     paramOrder: ['uid', 'foo']
94868 });
94869
94870 // load the form
94871 myFormPanel.getForm().load({
94872     // pass 2 arguments to server side getBasicInfo method (len=2)
94873     params: {
94874         foo: 'bar',
94875         uid: 34
94876     }
94877 });
94878  * </code></pre>
94879  * The data packet sent to the server will resemble something like:
94880  * <pre><code>
94881 [
94882     {
94883         "action":"Profile","method":"getBasicInfo","type":"rpc","tid":2,
94884         "data":[34,"bar"] // note the order of the params
94885     }
94886 ]
94887  * </code></pre>
94888  * The form will process a data packet returned by the server that is similar
94889  * to the following format:
94890  * <pre><code>
94891 [
94892     {
94893         "action":"Profile","method":"getBasicInfo","type":"rpc","tid":2,
94894         "result":{
94895             "success":true,
94896             "data":{
94897                 "name":"Fred Flintstone",
94898                 "company":"Slate Rock and Gravel",
94899                 "email":"fred.flintstone@slaterg.com"
94900             }
94901         }
94902     }
94903 ]
94904  * </code></pre>
94905  */
94906 Ext.define('Ext.form.action.DirectLoad', {
94907     extend:'Ext.form.action.Load',
94908     requires: ['Ext.direct.Manager'],
94909     alternateClassName: 'Ext.form.Action.DirectLoad',
94910     alias: 'formaction.directload',
94911
94912     type: 'directload',
94913
94914     run: function() {
94915         this.form.api.load.apply(window, this.getArgs());
94916     },
94917
94918     /**
94919      * @private
94920      * Build the arguments to be sent to the Direct call.
94921      * @return Array
94922      */
94923     getArgs: function() {
94924         var me = this,
94925             args = [],
94926             form = me.form,
94927             paramOrder = form.paramOrder,
94928             params = me.getParams(),
94929             i, len;
94930
94931         // If a paramOrder was specified, add the params into the argument list in that order.
94932         if (paramOrder) {
94933             for (i = 0, len = paramOrder.length; i < len; i++) {
94934                 args.push(params[paramOrder[i]]);
94935             }
94936         }
94937         // If paramsAsHash was specified, add all the params as a single object argument.
94938         else if (form.paramsAsHash) {
94939             args.push(params);
94940         }
94941
94942         // Add the callback and scope to the end of the arguments list
94943         args.push(me.onSuccess, me);
94944
94945         return args;
94946     },
94947
94948     // Direct actions have already been processed and therefore
94949     // we can directly set the result; Direct Actions do not have
94950     // a this.response property.
94951     processResponse: function(result) {
94952         return (this.result = result);
94953     },
94954
94955     onSuccess: function(result, trans) {
94956         if (trans.type == Ext.direct.Manager.self.exceptions.SERVER) {
94957             result = {};
94958         }
94959         this.callParent([result]);
94960     }
94961 });
94962
94963
94964
94965 /**
94966  * @class Ext.form.action.DirectSubmit
94967  * @extends Ext.form.action.Submit
94968  * <p>Provides Ext.direct support for submitting form data.</p>
94969  * <p>This example illustrates usage of Ext.direct.Direct to <b>submit</b> a form through Ext.Direct.</p>
94970  * <pre><code>
94971 var myFormPanel = new Ext.form.Panel({
94972     // configs for FormPanel
94973     title: 'Basic Information',
94974     renderTo: document.body,
94975     width: 300, height: 160,
94976     padding: 10,
94977     buttons:[{
94978         text: 'Submit',
94979         handler: function(){
94980             myFormPanel.getForm().submit({
94981                 params: {
94982                     foo: 'bar',
94983                     uid: 34
94984                 }
94985             });
94986         }
94987     }],
94988
94989     // configs apply to child items
94990     defaults: {anchor: '100%'},
94991     defaultType: 'textfield',
94992     items: [{
94993         fieldLabel: 'Name',
94994         name: 'name'
94995     },{
94996         fieldLabel: 'Email',
94997         name: 'email'
94998     },{
94999         fieldLabel: 'Company',
95000         name: 'company'
95001     }],
95002
95003     // configs for BasicForm
95004     api: {
95005         // The server-side method to call for load() requests
95006         load: Profile.getBasicInfo,
95007         // The server-side must mark the submit handler as a 'formHandler'
95008         submit: Profile.updateBasicInfo
95009     },
95010     // specify the order for the passed params
95011     paramOrder: ['uid', 'foo']
95012 });
95013  * </code></pre>
95014  * The data packet sent to the server will resemble something like:
95015  * <pre><code>
95016 {
95017     "action":"Profile","method":"updateBasicInfo","type":"rpc","tid":"6",
95018     "result":{
95019         "success":true,
95020         "id":{
95021             "extAction":"Profile","extMethod":"updateBasicInfo",
95022             "extType":"rpc","extTID":"6","extUpload":"false",
95023             "name":"Aaron Conran","email":"aaron@sencha.com","company":"Sencha Inc."
95024         }
95025     }
95026 }
95027  * </code></pre>
95028  * The form will process a data packet returned by the server that is similar
95029  * to the following:
95030  * <pre><code>
95031 // sample success packet (batched requests)
95032 [
95033     {
95034         "action":"Profile","method":"updateBasicInfo","type":"rpc","tid":3,
95035         "result":{
95036             "success":true
95037         }
95038     }
95039 ]
95040
95041 // sample failure packet (one request)
95042 {
95043         "action":"Profile","method":"updateBasicInfo","type":"rpc","tid":"6",
95044         "result":{
95045             "errors":{
95046                 "email":"already taken"
95047             },
95048             "success":false,
95049             "foo":"bar"
95050         }
95051 }
95052  * </code></pre>
95053  * Also see the discussion in {@link Ext.form.action.DirectLoad}.
95054  */
95055 Ext.define('Ext.form.action.DirectSubmit', {
95056     extend:'Ext.form.action.Submit',
95057     requires: ['Ext.direct.Manager'],
95058     alternateClassName: 'Ext.form.Action.DirectSubmit',
95059     alias: 'formaction.directsubmit',
95060
95061     type: 'directsubmit',
95062
95063     doSubmit: function() {
95064         var me = this,
95065             callback = Ext.Function.bind(me.onSuccess, me),
95066             formEl = me.buildForm();
95067         me.form.api.submit(formEl, callback, me);
95068         Ext.removeNode(formEl);
95069     },
95070
95071     // Direct actions have already been processed and therefore
95072     // we can directly set the result; Direct Actions do not have
95073     // a this.response property.
95074     processResponse: function(result) {
95075         return (this.result = result);
95076     },
95077
95078     onSuccess: function(response, trans) {
95079         if (trans.type === Ext.direct.Manager.self.exceptions.SERVER) {
95080             response = {};
95081         }
95082         this.callParent([response]);
95083     }
95084 });
95085
95086 /**
95087  * @class Ext.form.action.StandardSubmit
95088  * @extends Ext.form.action.Submit
95089  * <p>A class which handles submission of data from {@link Ext.form.Basic Form}s using a standard
95090  * <tt>&lt;form&gt;</tt> element submit. It does not handle the response from the submit.</p>
95091  * <p>If validation of the form fields fails, the Form's {@link Ext.form.Basic#afterAction} method
95092  * will be called. Otherwise, afterAction will not be called.</p>
95093  * <p>Instances of this class are only created by a {@link Ext.form.Basic Form} when
95094  * {@link Ext.form.Basic#submit submit}ting, when the form's {@link Ext.form.Basic#standardSubmit}
95095  * config option is <tt>true</tt>.</p>
95096  */
95097 Ext.define('Ext.form.action.StandardSubmit', {
95098     extend:'Ext.form.action.Submit',
95099     alias: 'formaction.standardsubmit',
95100
95101     /**
95102      * @cfg {String} target
95103      * Optional <tt>target</tt> attribute to be used for the form when submitting. If not specified,
95104      * the target will be the current window/frame.
95105      */
95106
95107     /**
95108      * @private
95109      * Perform the form submit. Creates and submits a temporary form element containing an input element for each
95110      * field value returned by {@link Ext.form.Basic#getValues}, plus any configured {@link #params params} or
95111      * {@link Ext.form.Basic#baseParams baseParams}.
95112      */
95113     doSubmit: function() {
95114         var form = this.buildForm();
95115         form.submit();
95116         Ext.removeNode(form);
95117     }
95118
95119 });
95120
95121 /**
95122  * @class Ext.form.field.Checkbox
95123  * @extends Ext.form.field.Base
95124
95125 Single checkbox field. Can be used as a direct replacement for traditional checkbox fields. Also serves as a
95126 parent class for {@link Ext.form.field.Radio radio buttons}.
95127
95128 __Labeling:__ In addition to the {@link Ext.form.Labelable standard field labeling options}, checkboxes
95129 may be given an optional {@link #boxLabel} which will be displayed immediately after checkbox. Also see
95130 {@link Ext.form.CheckboxGroup} for a convenient method of grouping related checkboxes.
95131
95132 __Values:__
95133 The main value of a checkbox is a boolean, indicating whether or not the checkbox is checked.
95134 The following values will check the checkbox:
95135 * `true`
95136 * `'true'`
95137 * `'1'`
95138 * `'on'`
95139
95140 Any other value will uncheck the checkbox.
95141
95142 In addition to the main boolean value, you may also specify a separate {@link #inputValue}. This will be
95143 sent as the parameter value when the form is {@link Ext.form.Basic#submit submitted}. You will want to set
95144 this value if you have multiple checkboxes with the same {@link #name}. If not specified, the value `on`
95145 will be used.
95146 {@img Ext.form.Checkbox/Ext.form.Checkbox.png Ext.form.Checkbox Checkbox component}
95147 __Example usage:__
95148
95149     Ext.create('Ext.form.Panel', {
95150         bodyPadding: 10,
95151         width      : 300,
95152         title      : 'Pizza Order',
95153         items: [
95154             {
95155                 xtype      : 'fieldcontainer',
95156                 fieldLabel : 'Toppings',
95157                 defaultType: 'checkboxfield',
95158                 items: [
95159                     {
95160                         boxLabel  : 'Anchovies',
95161                         name      : 'topping',
95162                         inputValue: '1',
95163                         id        : 'checkbox1'
95164                     }, {
95165                         boxLabel  : 'Artichoke Hearts',
95166                         name      : 'topping',
95167                         inputValue: '2',
95168                         checked   : true,
95169                         id        : 'checkbox2'
95170                     }, {
95171                         boxLabel  : 'Bacon',
95172                         name      : 'topping',
95173                         inputValue: '3',
95174                         id        : 'checkbox3'
95175                     }
95176                 ]
95177             }
95178         ],
95179         bbar: [
95180             {
95181                 text: 'Select Bacon',
95182                 handler: function() {
95183                     var checkbox = Ext.getCmp('checkbox3');
95184                     checkbox.setValue(true);
95185                 }
95186             },
95187             '-',
95188             {
95189                 text: 'Select All',
95190                 handler: function() {
95191                     var checkbox1 = Ext.getCmp('checkbox1'),
95192                         checkbox2 = Ext.getCmp('checkbox2'),
95193                         checkbox3 = Ext.getCmp('checkbox3');
95194     
95195                     checkbox1.setValue(true);
95196                     checkbox2.setValue(true);
95197                     checkbox3.setValue(true);
95198                 }
95199             },
95200             {
95201                 text: 'Deselect All',
95202                 handler: function() {
95203                     var checkbox1 = Ext.getCmp('checkbox1'),
95204                         checkbox2 = Ext.getCmp('checkbox2'),
95205                         checkbox3 = Ext.getCmp('checkbox3');
95206     
95207                     checkbox1.setValue(false);
95208                     checkbox2.setValue(false);
95209                     checkbox3.setValue(false);
95210                 }
95211             }
95212         ],
95213         renderTo: Ext.getBody()
95214     });
95215
95216  * @constructor
95217  * Creates a new Checkbox
95218  * @param {Object} config Configuration options
95219  * @xtype checkboxfield
95220  * @docauthor Robert Dougan <rob@sencha.com>
95221  * @markdown
95222  */
95223 Ext.define('Ext.form.field.Checkbox', {
95224     extend: 'Ext.form.field.Base',
95225     alias: ['widget.checkboxfield', 'widget.checkbox'],
95226     alternateClassName: 'Ext.form.Checkbox',
95227     requires: ['Ext.XTemplate', 'Ext.form.CheckboxManager'],
95228
95229     fieldSubTpl: [
95230         '<tpl if="boxLabel && boxLabelAlign == \'before\'">',
95231             '<label class="{boxLabelCls} {boxLabelCls}-{boxLabelAlign}" for="{id}">{boxLabel}</label>',
95232         '</tpl>',
95233         // Creates not an actual checkbox, but a button which is given aria role="checkbox" and
95234         // styled with a custom checkbox image. This allows greater control and consistency in
95235         // styling, and using a button allows it to gain focus and handle keyboard nav properly.
95236         '<input type="button" id="{id}" ',
95237             '<tpl if="tabIdx">tabIndex="{tabIdx}" </tpl>',
95238             'class="{fieldCls} {typeCls}" autocomplete="off" hidefocus="true" />',
95239         '<tpl if="boxLabel && boxLabelAlign == \'after\'">',
95240             '<label class="{boxLabelCls} {boxLabelCls}-{boxLabelAlign}" for="{id}">{boxLabel}</label>',
95241         '</tpl>',
95242         {
95243             disableFormats: true,
95244             compiled: true
95245         }
95246     ],
95247
95248     isCheckbox: true,
95249
95250     /**
95251      * @cfg {String} focusCls The CSS class to use when the checkbox receives focus
95252      * (defaults to <tt>'x-form-cb-focus'</tt>)
95253      */
95254     focusCls: Ext.baseCSSPrefix + 'form-cb-focus',
95255
95256     /**
95257      * @cfg {String} fieldCls The default CSS class for the checkbox (defaults to <tt>'x-form-field'</tt>)
95258      */
95259
95260     /**
95261      * @cfg {String} fieldBodyCls
95262      * An extra CSS class to be applied to the body content element in addition to {@link #fieldBodyCls}.
95263      * Defaults to 'x-form-cb-wrap.
95264      */
95265     fieldBodyCls: Ext.baseCSSPrefix + 'form-cb-wrap',
95266
95267     /**
95268      * @cfg {Boolean} checked <tt>true</tt> if the checkbox should render initially checked (defaults to <tt>false</tt>)
95269      */
95270     checked: false,
95271
95272     /**
95273      * @cfg {String} checkedCls The CSS class added to the component's main element when it is in the checked state.
95274      */
95275     checkedCls: Ext.baseCSSPrefix + 'form-cb-checked',
95276
95277     /**
95278      * @cfg {String} boxLabel An optional text label that will appear next to the checkbox. Whether it appears before
95279      * or after the checkbox is determined by the {@link #boxLabelAlign} config (defaults to after).
95280      */
95281
95282     /**
95283      * @cfg {String} boxLabelCls The CSS class to be applied to the {@link #boxLabel} element
95284      */
95285     boxLabelCls: Ext.baseCSSPrefix + 'form-cb-label',
95286
95287     /**
95288      * @cfg {String} boxLabelAlign The position relative to the checkbox where the {@link #boxLabel} should
95289      * appear. Recognized values are <tt>'before'</tt> and <tt>'after'</tt>. Defaults to <tt>'after'</tt>.
95290      */
95291     boxLabelAlign: 'after',
95292
95293     /**
95294      * @cfg {String} inputValue The value that should go into the generated input element's value attribute and
95295      * should be used as the parameter value when submitting as part of a form. Defaults to <tt>"on"</tt>.
95296      */
95297     inputValue: 'on',
95298
95299     /**
95300      * @cfg {String} uncheckedValue If configured, this will be submitted as the checkbox's value during form
95301      * submit if the checkbox is unchecked. By default this is undefined, which results in nothing being
95302      * submitted for the checkbox field when the form is submitted (the default behavior of HTML checkboxes).
95303      */
95304
95305     /**
95306      * @cfg {Function} handler A function called when the {@link #checked} value changes (can be used instead of
95307      * handling the {@link #change change event}). The handler is passed the following parameters:
95308      * <div class="mdetail-params"><ul>
95309      * <li><b>checkbox</b> : Ext.form.field.Checkbox<div class="sub-desc">The Checkbox being toggled.</div></li>
95310      * <li><b>checked</b> : Boolean<div class="sub-desc">The new checked state of the checkbox.</div></li>
95311      * </ul></div>
95312      */
95313
95314     /**
95315      * @cfg {Object} scope An object to use as the scope ('this' reference) of the {@link #handler} function
95316      * (defaults to this Checkbox).
95317      */
95318
95319     // private overrides
95320     checkChangeEvents: [],
95321     inputType: 'checkbox',
95322     ariaRole: 'checkbox',
95323
95324     // private
95325     onRe: /^on$/i,
95326
95327     initComponent: function(){
95328         this.callParent(arguments);
95329         this.getManager().add(this);
95330     },
95331
95332     initValue: function() {
95333         var me = this,
95334             checked = !!me.checked;
95335
95336         /**
95337          * The original value of the field as configured in the {@link #checked} configuration, or
95338          * as loaded by the last form load operation if the form's {@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad}
95339          * setting is <code>true</code>.
95340          * @type Mixed
95341          * @property originalValue
95342          */
95343         me.originalValue = me.lastValue = checked;
95344
95345         // Set the initial checked state
95346         me.setValue(checked);
95347     },
95348
95349     // private
95350     onRender : function(ct, position) {
95351         var me = this;
95352         Ext.applyIf(me.renderSelectors, {
95353             /**
95354              * @property boxLabelEl
95355              * @type Ext.core.Element
95356              * A reference to the label element created for the {@link #boxLabel}. Only present if the
95357              * component has been rendered and has a boxLabel configured.
95358              */
95359             boxLabelEl: 'label.' + me.boxLabelCls
95360         });
95361         Ext.applyIf(me.subTplData, {
95362             boxLabel: me.boxLabel,
95363             boxLabelCls: me.boxLabelCls,
95364             boxLabelAlign: me.boxLabelAlign
95365         });
95366
95367         me.callParent(arguments);
95368     },
95369
95370     initEvents: function() {
95371         var me = this;
95372         me.callParent();
95373         me.mon(me.inputEl, 'click', me.onBoxClick, me);
95374     },
95375
95376     /**
95377      * @private Handle click on the checkbox button
95378      */
95379     onBoxClick: function(e) {
95380         var me = this;
95381         if (!me.disabled && !me.readOnly) {
95382             this.setValue(!this.checked);
95383         }
95384     },
95385
95386     /**
95387      * Returns the checked state of the checkbox.
95388      * @return {Boolean} True if checked, else false
95389      */
95390     getRawValue: function() {
95391         return this.checked;
95392     },
95393
95394     /**
95395      * Returns the checked state of the checkbox.
95396      * @return {Boolean} True if checked, else false
95397      */
95398     getValue: function() {
95399         return this.checked;
95400     },
95401
95402     /**
95403      * Returns the submit value for the checkbox which can be used when submitting forms.
95404      * @return {Boolean/null} True if checked; otherwise either the {@link #uncheckedValue} or null.
95405      */
95406     getSubmitValue: function() {
95407         return this.checked ? this.inputValue : (this.uncheckedValue || null);
95408     },
95409
95410     getModelData: function() {
95411         return this.getSubmitData();
95412     },
95413
95414     /**
95415      * Sets the checked state of the checkbox.
95416      * @param {Boolean/String} value The following values will check the checkbox:
95417      * <code>true, 'true', '1', or 'on'</code>, as well as a String that matches the {@link #inputValue}.
95418      * Any other value will uncheck the checkbox.
95419      * @return {Boolean} the new checked state of the checkbox
95420      */
95421     setRawValue: function(value) {
95422         var me = this,
95423             inputEl = me.inputEl,
95424             inputValue = me.inputValue,
95425             checked = (value === true || value === 'true' || value === '1' ||
95426                       ((Ext.isString(value) && inputValue) ? value == inputValue : me.onRe.test(value)));
95427
95428         if (inputEl) {
95429             inputEl.dom.setAttribute('aria-checked', checked);
95430             me[checked ? 'addCls' : 'removeCls'](me.checkedCls);
95431         }
95432
95433         me.checked = me.rawValue = checked;
95434         return checked;
95435     },
95436
95437     /**
95438      * Sets the checked state of the checkbox, and invokes change detection.
95439      * @param {Boolean/String} checked The following values will check the checkbox:
95440      * <code>true, 'true', '1', or 'on'</code>, as well as a String that matches the {@link #inputValue}.
95441      * Any other value will uncheck the checkbox.
95442      * @return {Ext.form.field.Checkbox} this
95443      */
95444     setValue: function(checked) {
95445         var me = this;
95446
95447         // If an array of strings is passed, find all checkboxes in the group with the same name as this
95448         // one and check all those whose inputValue is in the array, unchecking all the others. This is to
95449         // facilitate setting values from Ext.form.Basic#setValues, but is not publicly documented as we
95450         // don't want users depending on this behavior.
95451         if (Ext.isArray(checked)) {
95452             me.getManager().getByName(me.name).each(function(cb) {
95453                 cb.setValue(Ext.Array.contains(checked, cb.inputValue));
95454             });
95455         } else {
95456             me.callParent(arguments);
95457         }
95458
95459         return me;
95460     },
95461
95462     // private
95463     valueToRaw: function(value) {
95464         // No extra conversion for checkboxes
95465         return value;
95466     },
95467
95468     /**
95469      * @private
95470      * Called when the checkbox's checked state changes. Invokes the {@link #handler} callback
95471      * function if specified.
95472      */
95473     onChange: function(newVal, oldVal) {
95474         var me = this,
95475             handler = me.handler;
95476         if (handler) {
95477             handler.call(me.scope || me, me, newVal);
95478         }
95479         me.callParent(arguments);
95480     },
95481
95482     // inherit docs
95483     getManager: function() {
95484         return Ext.form.CheckboxManager;
95485     },
95486
95487     onEnable: function() {
95488         var me = this,
95489             inputEl = me.inputEl;
95490         me.callParent();
95491         if (inputEl) {
95492             // Can still be disabled if the field is readOnly
95493             inputEl.dom.disabled = me.readOnly;
95494         }
95495     },
95496
95497     setReadOnly: function(readOnly) {
95498         var me = this,
95499             inputEl = me.inputEl;
95500         if (inputEl) {
95501             // Set the button to disabled when readonly
95502             inputEl.dom.disabled = readOnly || me.disabled;
95503         }
95504         me.readOnly = readOnly;
95505     },
95506
95507     /**
95508      * @protected Calculate and return the natural width of the bodyEl. It's possible that the initial
95509      * rendering will cause the boxLabel to wrap and give us a bad width, so we must prevent wrapping
95510      * while measuring.
95511      */
95512     getBodyNaturalWidth: function() {
95513         var me = this,
95514             bodyEl = me.bodyEl,
95515             ws = 'white-space',
95516             width;
95517         bodyEl.setStyle(ws, 'nowrap');
95518         width = bodyEl.getWidth();
95519         bodyEl.setStyle(ws, '');
95520         return width;
95521     }
95522
95523 });
95524
95525 /**
95526  * @private
95527  * @class Ext.layout.component.field.Trigger
95528  * @extends Ext.layout.component.field.Field
95529  * Layout class for {@link Ext.form.field.Trigger} fields. Adjusts the input field size to accommodate
95530  * the trigger button(s).
95531  * @private
95532  */
95533
95534 Ext.define('Ext.layout.component.field.Trigger', {
95535
95536     /* Begin Definitions */
95537
95538     alias: ['layout.triggerfield'],
95539
95540     extend: 'Ext.layout.component.field.Field',
95541
95542     /* End Definitions */
95543
95544     type: 'triggerfield',
95545
95546     sizeBodyContents: function(width, height) {
95547         var me = this,
95548             owner = me.owner,
95549             inputEl = owner.inputEl,
95550             triggerWrap = owner.triggerWrap,
95551             triggerWidth = owner.getTriggerWidth();
95552
95553         // If we or our ancestor is hidden, we can get a triggerWidth calculation
95554         // of 0.  We don't want to resize in this case.
95555         if (owner.hideTrigger || owner.readOnly || triggerWidth > 0) {
95556             // Decrease the field's width by the width of the triggers. Both the field and the triggerWrap
95557             // are floated left in CSS so they'll stack up side by side.
95558             me.setElementSize(inputEl, Ext.isNumber(width) ? width - triggerWidth : width);
95559     
95560             // Explicitly set the triggerWrap's width, to prevent wrapping
95561             triggerWrap.setWidth(triggerWidth);
95562         }
95563     }
95564 });
95565 /**
95566  * @class Ext.view.View
95567  * @extends Ext.view.AbstractView
95568  *
95569  * A mechanism for displaying data using custom layout templates and formatting. DataView uses an {@link Ext.XTemplate}
95570  * as its internal templating mechanism, and is bound to an {@link Ext.data.Store}
95571  * so that as the data in the store changes the view is automatically updated to reflect the changes.  The view also
95572  * provides built-in behavior for many common events that can occur for its contained items including click, doubleclick,
95573  * mouseover, mouseout, etc. as well as a built-in selection model. <b>In order to use these features, an {@link #itemSelector}
95574  * config must be provided for the DataView to determine what nodes it will be working with.</b>
95575  *
95576  * <p>The example below binds a DataView to a {@link Ext.data.Store} and renders it into an {@link Ext.panel.Panel}.</p>
95577  * {@img Ext.DataView/Ext.DataView.png Ext.DataView component}
95578  * <pre><code>
95579     Ext.regModel('Image', {
95580         Fields: [
95581             {name:'src', type:'string'},
95582             {name:'caption', type:'string'}
95583         ]
95584     });
95585     
95586     Ext.create('Ext.data.Store', {
95587         id:'imagesStore',
95588         model: 'Image',
95589         data: [
95590             {src:'http://www.sencha.com/img/20110215-feat-drawing.png', caption:'Drawing & Charts'},
95591             {src:'http://www.sencha.com/img/20110215-feat-data.png', caption:'Advanced Data'},
95592             {src:'http://www.sencha.com/img/20110215-feat-html5.png', caption:'Overhauled Theme'},
95593             {src:'http://www.sencha.com/img/20110215-feat-perf.png', caption:'Performance Tuned'}            
95594         ]
95595     });
95596     
95597     var imageTpl = new Ext.XTemplate(
95598         '<tpl for=".">',
95599             '<div style="thumb-wrap">',
95600               '<img src="{src}" />',
95601               '<br/><span>{caption}</span>',
95602             '</div>',
95603         '</tpl>'
95604     );
95605     
95606     Ext.create('Ext.DataView', {
95607         store: Ext.data.StoreManager.lookup('imagesStore'),
95608         tpl: imageTpl,
95609         itemSelector: 'div.thumb-wrap',
95610         emptyText: 'No images available',
95611         renderTo: Ext.getBody()
95612     });
95613  * </code></pre>
95614  * @xtype dataview
95615  */
95616 Ext.define('Ext.view.View', {
95617     extend: 'Ext.view.AbstractView',
95618     alternateClassName: 'Ext.view.View',
95619     alias: 'widget.dataview',
95620     
95621     inheritableStatics: {
95622         EventMap: {
95623             mousedown: 'MouseDown',
95624             mouseup: 'MouseUp',
95625             click: 'Click',
95626             dblclick: 'DblClick',
95627             contextmenu: 'ContextMenu',
95628             mouseover: 'MouseOver',
95629             mouseout: 'MouseOut',
95630             mouseenter: 'MouseEnter',
95631             mouseleave: 'MouseLeave',
95632             keydown: 'KeyDown'
95633         }
95634     },
95635     
95636     addCmpEvents: function() {
95637         this.addEvents(
95638             /**
95639              * @event beforeitemmousedown
95640              * Fires before the mousedown event on an item is processed. Returns false to cancel the default action.
95641              * @param {Ext.view.View} this
95642              * @param {Ext.data.Model} record The record that belongs to the item
95643              * @param {HTMLElement} item The item's element
95644              * @param {Number} index The item's index
95645              * @param {Ext.EventObject} e The raw event object
95646              */
95647             'beforeitemmousedown',
95648             /**
95649              * @event beforeitemmouseup
95650              * Fires before the mouseup event on an item is processed. Returns false to cancel the default action.
95651              * @param {Ext.view.View} this
95652              * @param {Ext.data.Model} record The record that belongs to the item
95653              * @param {HTMLElement} item The item's element
95654              * @param {Number} index The item's index
95655              * @param {Ext.EventObject} e The raw event object
95656              */
95657             'beforeitemmouseup',
95658             /**
95659              * @event beforeitemmouseenter
95660              * Fires before the mouseenter event on an item is processed. Returns false to cancel the default action.
95661              * @param {Ext.view.View} this
95662              * @param {Ext.data.Model} record The record that belongs to the item
95663              * @param {HTMLElement} item The item's element
95664              * @param {Number} index The item's index
95665              * @param {Ext.EventObject} e The raw event object
95666              */
95667             'beforeitemmouseenter',
95668             /**
95669              * @event beforeitemmouseleave
95670              * Fires before the mouseleave event on an item is processed. Returns false to cancel the default action.
95671              * @param {Ext.view.View} this
95672              * @param {Ext.data.Model} record The record that belongs to the item
95673              * @param {HTMLElement} item The item's element
95674              * @param {Number} index The item's index
95675              * @param {Ext.EventObject} e The raw event object
95676              */
95677             'beforeitemmouseleave',
95678             /**
95679              * @event beforeitemclick
95680              * Fires before the click event on an item is processed. Returns false to cancel the default action.
95681              * @param {Ext.view.View} this
95682              * @param {Ext.data.Model} record The record that belongs to the item
95683              * @param {HTMLElement} item The item's element
95684              * @param {Number} index The item's index
95685              * @param {Ext.EventObject} e The raw event object
95686              */
95687             'beforeitemclick',
95688             /**
95689              * @event beforeitemdblclick
95690              * Fires before the dblclick event on an item is processed. Returns false to cancel the default action.
95691              * @param {Ext.view.View} this
95692              * @param {Ext.data.Model} record The record that belongs to the item
95693              * @param {HTMLElement} item The item's element
95694              * @param {Number} index The item's index
95695              * @param {Ext.EventObject} e The raw event object
95696              */
95697             'beforeitemdblclick',
95698             /**
95699              * @event beforeitemcontextmenu
95700              * Fires before the contextmenu event on an item is processed. Returns false to cancel the default action.
95701              * @param {Ext.view.View} this
95702              * @param {Ext.data.Model} record The record that belongs to the item
95703              * @param {HTMLElement} item The item's element
95704              * @param {Number} index The item's index
95705              * @param {Ext.EventObject} e The raw event object
95706              */
95707             'beforeitemcontextmenu',
95708             /**
95709              * @event beforeitemkeydown
95710              * Fires before the keydown event on an item is processed. Returns false to cancel the default action.
95711              * @param {Ext.view.View} this
95712              * @param {Ext.data.Model} record The record that belongs to the item
95713              * @param {HTMLElement} item The item's element
95714              * @param {Number} index The item's index
95715              * @param {Ext.EventObject} e The raw event object. Use {@link Ext.EventObject#getKey getKey()} to retrieve the key that was pressed.
95716              */
95717             'beforeitemkeydown',
95718             /**
95719              * @event itemmousedown
95720              * Fires when there is a mouse down on an item
95721              * @param {Ext.view.View} this
95722              * @param {Ext.data.Model} record The record that belongs to the item
95723              * @param {HTMLElement} item The item's element
95724              * @param {Number} index The item's index
95725              * @param {Ext.EventObject} e The raw event object
95726              */
95727             'itemmousedown',
95728             /**
95729              * @event itemmouseup
95730              * Fires when there is a mouse up on an item
95731              * @param {Ext.view.View} this
95732              * @param {Ext.data.Model} record The record that belongs to the item
95733              * @param {HTMLElement} item The item's element
95734              * @param {Number} index The item's index
95735              * @param {Ext.EventObject} e The raw event object
95736              */
95737             'itemmouseup',
95738             /**
95739              * @event itemmouseenter
95740              * Fires when the mouse enters an item.
95741              * @param {Ext.view.View} this
95742              * @param {Ext.data.Model} record The record that belongs to the item
95743              * @param {HTMLElement} item The item's element
95744              * @param {Number} index The item's index
95745              * @param {Ext.EventObject} e The raw event object
95746              */
95747             'itemmouseenter',
95748             /**
95749              * @event itemmouseleave
95750              * Fires when the mouse leaves an item.
95751              * @param {Ext.view.View} this
95752              * @param {Ext.data.Model} record The record that belongs to the item
95753              * @param {HTMLElement} item The item's element
95754              * @param {Number} index The item's index
95755              * @param {Ext.EventObject} e The raw event object
95756              */
95757             'itemmouseleave',
95758             /**
95759              * @event itemclick
95760              * Fires when an item is clicked.
95761              * @param {Ext.view.View} this
95762              * @param {Ext.data.Model} record The record that belongs to the item
95763              * @param {HTMLElement} item The item's element
95764              * @param {Number} index The item's index
95765              * @param {Ext.EventObject} e The raw event object
95766              */
95767             'itemclick',
95768             /**
95769              * @event itemdblclick
95770              * Fires when an item is double clicked.
95771              * @param {Ext.view.View} this
95772              * @param {Ext.data.Model} record The record that belongs to the item
95773              * @param {HTMLElement} item The item's element
95774              * @param {Number} index The item's index
95775              * @param {Ext.EventObject} e The raw event object
95776              */
95777             'itemdblclick',
95778             /**
95779              * @event itemcontextmenu
95780              * Fires when an item is right clicked.
95781              * @param {Ext.view.View} this
95782              * @param {Ext.data.Model} record The record that belongs to the item
95783              * @param {HTMLElement} item The item's element
95784              * @param {Number} index The item's index
95785              * @param {Ext.EventObject} e The raw event object
95786              */
95787             'itemcontextmenu',
95788             /**
95789              * @event itemkeydown
95790              * Fires when a key is pressed while an item is currently selected.
95791              * @param {Ext.view.View} this
95792              * @param {Ext.data.Model} record The record that belongs to the item
95793              * @param {HTMLElement} item The item's element
95794              * @param {Number} index The item's index
95795              * @param {Ext.EventObject} e The raw event object. Use {@link Ext.EventObject#getKey getKey()} to retrieve the key that was pressed.
95796              */
95797             'itemkeydown',
95798             /**
95799              * @event beforecontainermousedown
95800              * Fires before the mousedown event on the container is processed. Returns false to cancel the default action.
95801              * @param {Ext.view.View} this
95802              * @param {Ext.EventObject} e The raw event object
95803              */
95804             'beforecontainermousedown',
95805             /**
95806              * @event beforecontainermouseup
95807              * Fires before the mouseup event on the container is processed. Returns false to cancel the default action.
95808              * @param {Ext.view.View} this
95809              * @param {Ext.EventObject} e The raw event object
95810              */
95811             'beforecontainermouseup',
95812             /**
95813              * @event beforecontainermouseover
95814              * Fires before the mouseover event on the container is processed. Returns false to cancel the default action.
95815              * @param {Ext.view.View} this
95816              * @param {Ext.EventObject} e The raw event object
95817              */
95818             'beforecontainermouseover',
95819             /**
95820              * @event beforecontainermouseout
95821              * Fires before the mouseout event on the container is processed. Returns false to cancel the default action.
95822              * @param {Ext.view.View} this
95823              * @param {Ext.EventObject} e The raw event object
95824              */
95825             'beforecontainermouseout',
95826             /**
95827              * @event beforecontainerclick
95828              * Fires before the click event on the container is processed. Returns false to cancel the default action.
95829              * @param {Ext.view.View} this
95830              * @param {Ext.EventObject} e The raw event object
95831              */
95832             'beforecontainerclick',
95833             /**
95834              * @event beforecontainerdblclick
95835              * Fires before the dblclick event on the container is processed. Returns false to cancel the default action.
95836              * @param {Ext.view.View} this
95837              * @param {Ext.EventObject} e The raw event object
95838              */
95839             'beforecontainerdblclick',
95840             /**
95841              * @event beforecontainercontextmenu
95842              * Fires before the contextmenu event on the container is processed. Returns false to cancel the default action.
95843              * @param {Ext.view.View} this
95844              * @param {Ext.EventObject} e The raw event object
95845              */
95846             'beforecontainercontextmenu',
95847             /**
95848              * @event beforecontainerkeydown
95849              * Fires before the keydown event on the container is processed. Returns false to cancel the default action.
95850              * @param {Ext.view.View} this
95851              * @param {Ext.EventObject} e The raw event object. Use {@link Ext.EventObject#getKey getKey()} to retrieve the key that was pressed.
95852              */
95853             'beforecontainerkeydown',
95854             /**
95855              * @event containermouseup
95856              * Fires when there is a mouse up on the container
95857              * @param {Ext.view.View} this
95858              * @param {Ext.EventObject} e The raw event object
95859              */
95860             'containermouseup',
95861             /**
95862              * @event containermouseover
95863              * Fires when you move the mouse over the container.
95864              * @param {Ext.view.View} this
95865              * @param {Ext.EventObject} e The raw event object
95866              */
95867             'containermouseover',
95868             /**
95869              * @event containermouseout
95870              * Fires when you move the mouse out of the container.
95871              * @param {Ext.view.View} this
95872              * @param {Ext.EventObject} e The raw event object
95873              */
95874             'containermouseout',
95875             /**
95876              * @event containerclick
95877              * Fires when the container is clicked.
95878              * @param {Ext.view.View} this
95879              * @param {Ext.EventObject} e The raw event object
95880              */
95881             'containerclick',
95882             /**
95883              * @event containerdblclick
95884              * Fires when the container is double clicked.
95885              * @param {Ext.view.View} this
95886              * @param {Ext.EventObject} e The raw event object
95887              */
95888             'containerdblclick',
95889             /**
95890              * @event containercontextmenu
95891              * Fires when the container is right clicked.
95892              * @param {Ext.view.View} this
95893              * @param {Ext.EventObject} e The raw event object
95894              */
95895             'containercontextmenu',
95896             /**
95897              * @event containerkeydown
95898              * Fires when a key is pressed while the container is focused, and no item is currently selected.
95899              * @param {Ext.view.View} this
95900              * @param {Ext.EventObject} e The raw event object. Use {@link Ext.EventObject#getKey getKey()} to retrieve the key that was pressed.
95901              */
95902             'containerkeydown',
95903             
95904             /**
95905              * @event selectionchange
95906              * Fires when the selected nodes change. Relayed event from the underlying selection model.
95907              * @param {Ext.view.View} this
95908              * @param {Array} selections Array of the selected nodes
95909              */
95910             'selectionchange',
95911             /**
95912              * @event beforeselect
95913              * Fires before a selection is made. If any handlers return false, the selection is cancelled.
95914              * @param {Ext.view.View} this
95915              * @param {HTMLElement} node The node to be selected
95916              * @param {Array} selections Array of currently selected nodes
95917              */
95918             'beforeselect'
95919         );
95920     },
95921     // private
95922     afterRender: function(){
95923         var me = this, 
95924             listeners;
95925         
95926         me.callParent();
95927
95928         listeners = {
95929             scope: me,
95930             click: me.handleEvent,
95931             mousedown: me.handleEvent,
95932             mouseup: me.handleEvent,
95933             dblclick: me.handleEvent,
95934             contextmenu: me.handleEvent,
95935             mouseover: me.handleEvent,
95936             mouseout: me.handleEvent,
95937             keydown: me.handleEvent
95938         };
95939         
95940         me.mon(me.getTargetEl(), listeners);
95941         
95942         if (me.store) {
95943             me.bindStore(me.store, true);
95944         }
95945     },
95946     
95947     handleEvent: function(e) {
95948         if (this.processUIEvent(e) !== false) {
95949             this.processSpecialEvent(e);
95950         }
95951     },
95952     
95953     // Private template method
95954     processItemEvent: Ext.emptyFn,
95955     processContainerEvent: Ext.emptyFn,
95956     processSpecialEvent: Ext.emptyFn,
95957     
95958     processUIEvent: function(e, type) {
95959         type = type || e.type;
95960         var me = this,
95961             item = e.getTarget(me.getItemSelector(), me.getTargetEl()),
95962             map = this.statics().EventMap,
95963             index, record;
95964         
95965         if (!item) {
95966             // There is this weird bug when you hover over the border of a cell it is saying
95967             // the target is the table.
95968             // BrowserBug: IE6 & 7. If me.mouseOverItem has been removed and is no longer
95969             // in the DOM then accessing .offsetParent will throw an "Unspecified error." exception.
95970             // typeof'ng and checking to make sure the offsetParent is an object will NOT throw
95971             // this hard exception.
95972             if (type == 'mouseover' && me.mouseOverItem && typeof me.mouseOverItem.offsetParent === "object" && Ext.fly(me.mouseOverItem).getRegion().contains(e.getPoint())) {
95973                 item = me.mouseOverItem;
95974             }
95975             
95976             // Try to get the selected item to handle the keydown event, otherwise we'll just fire a container keydown event
95977             if (type == 'keydown') {
95978                 record = me.getSelectionModel().getLastSelected();
95979                 if (record) {
95980                     item = me.getNode(record);
95981                 }
95982             }
95983         }
95984         
95985         if (item) {
95986             index = me.indexOf(item);
95987             if (!record) {
95988                 record = me.getRecord(item);
95989             }
95990             
95991             if (me.processItemEvent(type, record, item, index, e) === false) {
95992                 return false;
95993             }
95994             
95995             type = me.isNewItemEvent(type, item, e);
95996             if (type === false) {
95997                 return false;
95998             }
95999             
96000             if (
96001                 (me['onBeforeItem' + map[type]](record, item, index, e) === false) ||
96002                 (me.fireEvent('beforeitem' + type, me, record, item, index, e) === false) ||
96003                 (me['onItem' + map[type]](record, item, index, e) === false)
96004             ) { 
96005                 return false;
96006             }
96007             
96008             me.fireEvent('item' + type, me, record, item, index, e);
96009         } 
96010         else {
96011             if (
96012                 (me.processContainerEvent(type, e) === false) ||
96013                 (me['onBeforeContainer' + map[type]](e) === false) ||
96014                 (me.fireEvent('beforecontainer' + type, me, e) === false) ||
96015                 (me['onContainer' + map[type]](e) === false)
96016             ) {
96017                 return false;
96018             }
96019             
96020             me.fireEvent('container' + type, me, e);
96021         }
96022         
96023         return true;
96024     },
96025     
96026     isNewItemEvent: function(type, item, e) {
96027         var me = this,
96028             overItem = me.mouseOverItem,
96029             contains,
96030             isItem;
96031             
96032         switch (type) {
96033             case 'mouseover':
96034                 if (item === overItem) {
96035                     return false;
96036                 }
96037                 me.mouseOverItem = item;
96038                 return 'mouseenter';
96039             break;
96040             
96041             case 'mouseout':
96042                /*
96043                 * Need an extra check here to see if it's the parent element. See the
96044                 * comment re: the browser bug at the start of processUIEvent
96045                 */
96046                 if (overItem && typeof overItem.offsetParent === "object") {
96047                     contains = Ext.fly(me.mouseOverItem).getRegion().contains(e.getPoint());
96048                     isItem = Ext.fly(e.getTarget()).hasCls(me.itemSelector);
96049                     if (contains && isItem) {
96050                         return false;
96051                     }
96052                 }
96053                 me.mouseOverItem = null;
96054                 return 'mouseleave';
96055             break;
96056         }
96057         return type;
96058     },
96059     
96060     // private
96061     onItemMouseEnter: function(record, item, index, e) {
96062         if (this.trackOver) {
96063             this.highlightItem(item);
96064         }
96065     },
96066
96067     // private
96068     onItemMouseLeave : function(record, item, index, e) {
96069         if (this.trackOver) {
96070             this.clearHighlight();
96071         }
96072     },
96073
96074     // @private, template methods
96075     onItemMouseDown: Ext.emptyFn,
96076     onItemMouseUp: Ext.emptyFn,
96077     onItemClick: Ext.emptyFn,
96078     onItemDblClick: Ext.emptyFn,
96079     onItemContextMenu: Ext.emptyFn,
96080     onItemKeyDown: Ext.emptyFn,
96081     onBeforeItemMouseDown: Ext.emptyFn,
96082     onBeforeItemMouseUp: Ext.emptyFn,
96083     onBeforeItemMouseEnter: Ext.emptyFn,
96084     onBeforeItemMouseLeave: Ext.emptyFn,
96085     onBeforeItemClick: Ext.emptyFn,
96086     onBeforeItemDblClick: Ext.emptyFn,
96087     onBeforeItemContextMenu: Ext.emptyFn,
96088     onBeforeItemKeyDown: Ext.emptyFn,
96089     
96090     // @private, template methods
96091     onContainerMouseDown: Ext.emptyFn,
96092     onContainerMouseUp: Ext.emptyFn,
96093     onContainerMouseOver: Ext.emptyFn,
96094     onContainerMouseOut: Ext.emptyFn,
96095     onContainerClick: Ext.emptyFn,
96096     onContainerDblClick: Ext.emptyFn,
96097     onContainerContextMenu: Ext.emptyFn,
96098     onContainerKeyDown: Ext.emptyFn,
96099     onBeforeContainerMouseDown: Ext.emptyFn,
96100     onBeforeContainerMouseUp: Ext.emptyFn,
96101     onBeforeContainerMouseOver: Ext.emptyFn,
96102     onBeforeContainerMouseOut: Ext.emptyFn,
96103     onBeforeContainerClick: Ext.emptyFn,
96104     onBeforeContainerDblClick: Ext.emptyFn,
96105     onBeforeContainerContextMenu: Ext.emptyFn,
96106     onBeforeContainerKeyDown: Ext.emptyFn,
96107     
96108     /**
96109      * Highlight a given item in the DataView. This is called by the mouseover handler if {@link #overItemCls}
96110      * and {@link #trackOver} are configured, but can also be called manually by other code, for instance to
96111      * handle stepping through the list via keyboard navigation.
96112      * @param {HTMLElement} item The item to highlight
96113      */
96114     highlightItem: function(item) {
96115         var me = this;
96116         me.clearHighlight();
96117         me.highlightedItem = item;
96118         Ext.fly(item).addCls(me.overItemCls);
96119     },
96120
96121     /**
96122      * Un-highlight the currently highlighted item, if any.
96123      */
96124     clearHighlight: function() {
96125         var me = this,
96126             highlighted = me.highlightedItem;
96127             
96128         if (highlighted) {
96129             Ext.fly(highlighted).removeCls(me.overItemCls);
96130             delete me.highlightedItem;
96131         }
96132     },
96133
96134     refresh: function() {
96135         this.clearHighlight();
96136         this.callParent(arguments);
96137     }
96138 });
96139 /**
96140  * Component layout for {@link Ext.view.BoundList}. Handles constraining the height to the configured maxHeight.
96141  * @class Ext.layout.component.BoundList
96142  * @extends Ext.layout.component.Component
96143  * @private
96144  */
96145 Ext.define('Ext.layout.component.BoundList', {
96146     extend: 'Ext.layout.component.Component',
96147     alias: 'layout.boundlist',
96148
96149     type: 'component',
96150
96151     beforeLayout: function() {
96152         return this.callParent(arguments) || this.owner.refreshed > 0;
96153     },
96154
96155     onLayout : function(width, height) {
96156         var me = this,
96157             owner = me.owner,
96158             floating = owner.floating,
96159             el = owner.el,
96160             xy = el.getXY(),
96161             isNumber = Ext.isNumber,
96162             minWidth, maxWidth, minHeight, maxHeight,
96163             naturalWidth, naturalHeight, constrainedWidth, constrainedHeight, undef;
96164
96165         if (floating) {
96166             // Position offscreen so the natural width is not affected by the viewport's right edge
96167             el.setXY([-9999,-9999]);
96168         }
96169
96170         // Calculate initial layout
96171         me.setTargetSize(width, height);
96172
96173         // Handle min/maxWidth for auto-width
96174         if (!isNumber(width)) {
96175             minWidth = owner.minWidth;
96176             maxWidth = owner.maxWidth;
96177             if (isNumber(minWidth) || isNumber(maxWidth)) {
96178                 naturalWidth = el.getWidth();
96179                 if (naturalWidth < minWidth) {
96180                     constrainedWidth = minWidth;
96181                 }
96182                 else if (naturalWidth > maxWidth) {
96183                     constrainedWidth = maxWidth;
96184                 }
96185                 if (constrainedWidth) {
96186                     me.setTargetSize(constrainedWidth);
96187                 }
96188             }
96189         }
96190         // Handle min/maxHeight for auto-height
96191         if (!isNumber(height)) {
96192             minHeight = owner.minHeight;
96193             maxHeight = owner.maxHeight;
96194             if (isNumber(minHeight) || isNumber(maxHeight)) {
96195                 naturalHeight = el.getHeight();
96196                 if (naturalHeight < minHeight) {
96197                     constrainedHeight = minHeight;
96198                 }
96199                 else if (naturalHeight > maxHeight) {
96200                     constrainedHeight = maxHeight;
96201                 }
96202                 if (constrainedHeight) {
96203                     me.setTargetSize(undef, constrainedHeight);
96204                 }
96205             }
96206         }
96207
96208         if (floating) {
96209             // Restore position
96210             el.setXY(xy);
96211         }
96212     },
96213
96214     afterLayout: function() {
96215         var me = this,
96216             toolbar = me.owner.pagingToolbar;
96217         me.callParent();
96218         if (toolbar) {
96219             toolbar.doComponentLayout();
96220         }
96221     },
96222
96223     setTargetSize : function(width, height) {
96224         var me = this,
96225             owner = me.owner,
96226             listHeight = null,
96227             toolbar;
96228
96229         // Size the listEl
96230         if (Ext.isNumber(height)) {
96231             listHeight = height - owner.el.getFrameWidth('tb');
96232             toolbar = owner.pagingToolbar;
96233             if (toolbar) {
96234                 listHeight -= toolbar.getHeight();
96235             }
96236         }
96237         me.setElementSize(owner.listEl, null, listHeight);
96238
96239         me.callParent(arguments);
96240     }
96241
96242 });
96243
96244 /**
96245  * @class Ext.toolbar.TextItem
96246  * @extends Ext.toolbar.Item
96247  *
96248  * A simple class that renders text directly into a toolbar.
96249  *
96250  * ## Example usage
96251  *
96252  * {@img Ext.toolbar.TextItem/Ext.toolbar.TextItem.png TextItem component}
96253  *
96254  *     Ext.create('Ext.panel.Panel', {
96255  *         title: 'Panel with TextItem',
96256  *         width: 300,
96257  *         height: 200,
96258  *         tbar: [
96259  *             {xtype: 'tbtext', text: 'Sample TextItem'}
96260  *         ],
96261  *         renderTo: Ext.getBody()
96262  *     });
96263  *
96264  * @constructor
96265  * Creates a new TextItem
96266  * @param {Object} text A text string, or a config object containing a <tt>text</tt> property
96267  * @xtype tbtext
96268  */
96269 Ext.define('Ext.toolbar.TextItem', {
96270     extend: 'Ext.toolbar.Item',
96271     requires: ['Ext.XTemplate'],
96272     alias: 'widget.tbtext',
96273     alternateClassName: 'Ext.Toolbar.TextItem',
96274     
96275     /**
96276      * @cfg {String} text The text to be used as innerHTML (html tags are accepted)
96277      */
96278     text: '',
96279     
96280     renderTpl: '{text}',
96281     //
96282     baseCls: Ext.baseCSSPrefix + 'toolbar-text',
96283     
96284     onRender : function() {
96285         Ext.apply(this.renderData, {
96286             text: this.text
96287         });
96288         this.callParent(arguments);
96289     },
96290
96291     /**
96292      * Updates this item's text, setting the text to be used as innerHTML.
96293      * @param {String} t The text to display (html accepted).
96294      */
96295     setText : function(t) {
96296         if (this.rendered) {
96297             this.el.update(t);
96298             this.ownerCt.doLayout(); // In case an empty text item (centered at zero height) receives new text.
96299         } else {
96300             this.text = t;
96301         }
96302     }
96303 });
96304 /**
96305  * @class Ext.form.field.Trigger
96306  * @extends Ext.form.field.Text
96307  * <p>Provides a convenient wrapper for TextFields that adds a clickable trigger button (looks like a combobox by default).
96308  * The trigger has no default action, so you must assign a function to implement the trigger click handler by
96309  * overriding {@link #onTriggerClick}. You can create a Trigger field directly, as it renders exactly like a combobox
96310  * for which you can provide a custom implementation. 
96311  * {@img Ext.form.field.Trigger/Ext.form.field.Trigger.png Ext.form.field.Trigger component}
96312  * For example:</p>
96313  * <pre><code>
96314     Ext.define('Ext.ux.CustomTrigger', {
96315         extend: 'Ext.form.field.Trigger',
96316         alias: 'widget.customtrigger',
96317         
96318         // override onTriggerClick
96319         onTriggerClick: function() {
96320             Ext.Msg.alert('Status', 'You clicked my trigger!');
96321         }
96322     });
96323     
96324     Ext.create('Ext.form.FormPanel', {
96325         title: 'Form with TriggerField',
96326         bodyPadding: 5,
96327         width: 350,
96328         renderTo: Ext.getBody(),
96329         items:[{
96330             xtype: 'customtrigger',
96331             fieldLabel: 'Sample Trigger',
96332             emptyText: 'click the trigger',
96333         }]
96334     });
96335 </code></pre>
96336  *
96337  * <p>However, in general you will most likely want to use Trigger as the base class for a reusable component.
96338  * {@link Ext.form.field.Date} and {@link Ext.form.field.ComboBox} are perfect examples of this.</p>
96339  *
96340  * @constructor
96341  * Create a new Trigger field.
96342  * @param {Object} config Configuration options (valid {@Ext.form.field.Text} config options will also be applied
96343  * to the base Text field)
96344  * @xtype triggerfield
96345  */
96346 Ext.define('Ext.form.field.Trigger', {
96347     extend:'Ext.form.field.Text',
96348     alias: ['widget.triggerfield', 'widget.trigger'],
96349     requires: ['Ext.core.DomHelper', 'Ext.util.ClickRepeater', 'Ext.layout.component.field.Trigger'],
96350     alternateClassName: ['Ext.form.TriggerField', 'Ext.form.TwinTriggerField', 'Ext.form.Trigger'],
96351
96352     fieldSubTpl: [
96353         '<input id="{id}" type="{type}" ',
96354             '<tpl if="name">name="{name}" </tpl>',
96355             '<tpl if="size">size="{size}" </tpl>',
96356             '<tpl if="tabIdx">tabIndex="{tabIdx}" </tpl>',
96357             'class="{fieldCls} {typeCls}" autocomplete="off" />',
96358         '<div class="{triggerWrapCls}" role="presentation">',
96359             '{triggerEl}',
96360             '<div class="{clearCls}" role="presentation"></div>',
96361         '</div>',
96362         {
96363             compiled: true,
96364             disableFormats: true
96365         }
96366     ],
96367
96368     /**
96369      * @cfg {String} triggerCls
96370      * An additional CSS class used to style the trigger button.  The trigger will always get the
96371      * {@link #triggerBaseCls} by default and <tt>triggerCls</tt> will be <b>appended</b> if specified.
96372      * Defaults to undefined.
96373      */
96374
96375     /**
96376      * @cfg {String} triggerBaseCls
96377      * The base CSS class that is always added to the trigger button. The {@link #triggerCls} will be
96378      * appended in addition to this class.
96379      */
96380     triggerBaseCls: Ext.baseCSSPrefix + 'form-trigger',
96381
96382     /**
96383      * @cfg {String} triggerWrapCls
96384      * The CSS class that is added to the div wrapping the trigger button(s).
96385      */
96386     triggerWrapCls: Ext.baseCSSPrefix + 'form-trigger-wrap',
96387
96388     /**
96389      * @cfg {Boolean} hideTrigger <tt>true</tt> to hide the trigger element and display only the base
96390      * text field (defaults to <tt>false</tt>)
96391      */
96392     hideTrigger: false,
96393
96394     /**
96395      * @cfg {Boolean} editable <tt>false</tt> to prevent the user from typing text directly into the field;
96396      * the field can only have its value set via an action invoked by the trigger. (defaults to <tt>true</tt>).
96397      */
96398     editable: true,
96399
96400     /**
96401      * @cfg {Boolean} readOnly <tt>true</tt> to prevent the user from changing the field, and
96402      * hides the trigger.  Supercedes the editable and hideTrigger options if the value is true.
96403      * (defaults to <tt>false</tt>)
96404      */
96405     readOnly: false,
96406
96407     /**
96408      * @cfg {Boolean} selectOnFocus <tt>true</tt> to select any existing text in the field immediately on focus.
96409      * Only applies when <tt>{@link #editable editable} = true</tt> (defaults to <tt>false</tt>).
96410      */
96411
96412     /**
96413      * @cfg {Boolean} repeatTriggerClick <tt>true</tt> to attach a {@link Ext.util.ClickRepeater click repeater}
96414      * to the trigger. Defaults to <tt>false</tt>.
96415      */
96416     repeatTriggerClick: false,
96417
96418
96419     /**
96420      * @hide
96421      * @method autoSize
96422      */
96423     autoSize: Ext.emptyFn,
96424     // private
96425     monitorTab: true,
96426     // private
96427     mimicing: false,
96428     // private
96429     triggerIndexRe: /trigger-index-(\d+)/,
96430
96431     componentLayout: 'triggerfield',
96432
96433     initComponent: function() {
96434         this.wrapFocusCls = this.triggerWrapCls + '-focus';
96435         this.callParent(arguments);
96436     },
96437
96438     // private
96439     onRender: function(ct, position) {
96440         var me = this,
96441             triggerCls,
96442             triggerBaseCls = me.triggerBaseCls,
96443             triggerWrapCls = me.triggerWrapCls,
96444             triggerConfigs = [],
96445             i;
96446
96447         // triggerCls is a synonym for trigger1Cls, so copy it.
96448         // TODO this trigger<n>Cls API design doesn't feel clean, especially where it butts up against the
96449         // single triggerCls config. Should rethink this, perhaps something more structured like a list of
96450         // trigger config objects that hold cls, handler, etc.
96451         if (!me.trigger1Cls) {
96452             me.trigger1Cls = me.triggerCls;
96453         }
96454
96455         // Create as many trigger elements as we have trigger<n>Cls configs, but always at least one
96456         for (i = 0; (triggerCls = me['trigger' + (i + 1) + 'Cls']) || i < 1; i++) {
96457             triggerConfigs.push({
96458                 cls: [Ext.baseCSSPrefix + 'trigger-index-' + i, triggerBaseCls, triggerCls].join(' '),
96459                 role: 'button'
96460             });
96461         }
96462         triggerConfigs[i - 1].cls += ' ' + triggerBaseCls + '-last';
96463
96464         Ext.applyIf(me.renderSelectors, {
96465             /**
96466              * @property triggerWrap
96467              * @type Ext.core.Element
96468              * A reference to the div element wrapping the trigger button(s). Only set after the field has been rendered.
96469              */
96470             triggerWrap: '.' + triggerWrapCls
96471         });
96472         Ext.applyIf(me.subTplData, {
96473             triggerWrapCls: triggerWrapCls,
96474             triggerEl: Ext.core.DomHelper.markup(triggerConfigs),
96475             clearCls: me.clearCls
96476         });
96477
96478         me.callParent(arguments);
96479
96480         /**
96481          * @property triggerEl
96482          * @type Ext.CompositeElement
96483          * A composite of all the trigger button elements. Only set after the field has been rendered.
96484          */
96485         me.triggerEl = Ext.select('.' + triggerBaseCls, true, me.triggerWrap.dom);
96486
96487         me.doc = Ext.isIE ? Ext.getBody() : Ext.getDoc();
96488         me.initTrigger();
96489     },
96490
96491     onEnable: function() {
96492         this.callParent();
96493         this.triggerWrap.unmask();
96494     },
96495     
96496     onDisable: function() {
96497         this.callParent();
96498         this.triggerWrap.mask();
96499     },
96500     
96501     afterRender: function() {
96502         this.callParent();
96503         this.updateEditState();
96504     },
96505
96506     updateEditState: function() {
96507         var me = this,
96508             inputEl = me.inputEl,
96509             triggerWrap = me.triggerWrap,
96510             noeditCls = Ext.baseCSSPrefix + 'trigger-noedit',
96511             displayed,
96512             readOnly;
96513
96514         if (me.rendered) {
96515             if (me.readOnly) {
96516                 inputEl.addCls(noeditCls);
96517                 readOnly = true;
96518                 displayed = false;
96519             } else {
96520                 if (me.editable) {
96521                     inputEl.removeCls(noeditCls);
96522                     readOnly = false;
96523                 } else {
96524                     inputEl.addCls(noeditCls);
96525                     readOnly = true;
96526                 }
96527                 displayed = !me.hideTrigger;
96528             }
96529
96530             triggerWrap.setDisplayed(displayed);
96531             inputEl.dom.readOnly = readOnly;
96532             me.doComponentLayout();
96533         }
96534     },
96535
96536     /**
96537      * Get the total width of the trigger button area. Only useful after the field has been rendered.
96538      * @return {Number} The trigger width
96539      */
96540     getTriggerWidth: function() {
96541         var me = this,
96542             triggerWrap = me.triggerWrap,
96543             totalTriggerWidth = 0;
96544         if (triggerWrap && !me.hideTrigger && !me.readOnly) {
96545             me.triggerEl.each(function(trigger) {
96546                 totalTriggerWidth += trigger.getWidth();
96547             });
96548             totalTriggerWidth += me.triggerWrap.getFrameWidth('lr');
96549         }
96550         return totalTriggerWidth;
96551     },
96552
96553     setHideTrigger: function(hideTrigger) {
96554         if (hideTrigger != this.hideTrigger) {
96555             this.hideTrigger = hideTrigger;
96556             this.updateEditState();
96557         }
96558     },
96559
96560     /**
96561      * @param {Boolean} editable True to allow the user to directly edit the field text
96562      * Allow or prevent the user from directly editing the field text.  If false is passed,
96563      * the user will only be able to modify the field using the trigger.  Will also add
96564      * a click event to the text field which will call the trigger. This method
96565      * is the runtime equivalent of setting the 'editable' config option at config time.
96566      */
96567     setEditable: function(editable) {
96568         if (editable != this.editable) {
96569             this.editable = editable;
96570             this.updateEditState();
96571         }
96572     },
96573
96574     /**
96575      * @param {Boolean} readOnly True to prevent the user changing the field and explicitly
96576      * hide the trigger.
96577      * Setting this to true will superceed settings editable and hideTrigger.
96578      * Setting this to false will defer back to editable and hideTrigger. This method
96579      * is the runtime equivalent of setting the 'readOnly' config option at config time.
96580      */
96581     setReadOnly: function(readOnly) {
96582         if (readOnly != this.readOnly) {
96583             this.readOnly = readOnly;
96584             this.updateEditState();
96585         }
96586     },
96587
96588     // private
96589     initTrigger: function() {
96590         var me = this,
96591             triggerWrap = me.triggerWrap,
96592             triggerEl = me.triggerEl;
96593
96594         if (me.repeatTriggerClick) {
96595             me.triggerRepeater = Ext.create('Ext.util.ClickRepeater', triggerWrap, {
96596                 preventDefault: true,
96597                 handler: function(cr, e) {
96598                     me.onTriggerWrapClick(e);
96599                 }
96600             });
96601         } else {
96602             me.mon(me.triggerWrap, 'click', me.onTriggerWrapClick, me);
96603         }
96604
96605         triggerEl.addClsOnOver(me.triggerBaseCls + '-over');
96606         triggerEl.each(function(el, c, i) {
96607             el.addClsOnOver(me['trigger' + (i + 1) + 'Cls'] + '-over');
96608         });
96609         triggerEl.addClsOnClick(me.triggerBaseCls + '-click');
96610         triggerEl.each(function(el, c, i) {
96611             el.addClsOnClick(me['trigger' + (i + 1) + 'Cls'] + '-click');
96612         });
96613     },
96614
96615     // private
96616     onDestroy: function() {
96617         var me = this;
96618         Ext.destroyMembers(me, 'triggerRepeater', 'triggerWrap', 'triggerEl');
96619         delete me.doc;
96620         me.callParent();
96621     },
96622
96623     // private
96624     onFocus: function() {
96625         var me = this;
96626         this.callParent();
96627         if (!me.mimicing) {
96628             me.bodyEl.addCls(me.wrapFocusCls);
96629             me.mimicing = true;
96630             me.mon(me.doc, 'mousedown', me.mimicBlur, me, {
96631                 delay: 10
96632             });
96633             if (me.monitorTab) {
96634                 me.on('specialkey', me.checkTab, me);
96635             }
96636         }
96637     },
96638
96639     // private
96640     checkTab: function(me, e) {
96641         if (!this.ignoreMonitorTab && e.getKey() == e.TAB) {
96642             this.triggerBlur();
96643         }
96644     },
96645
96646     // private
96647     onBlur: Ext.emptyFn,
96648
96649     // private
96650     mimicBlur: function(e) {
96651         if (!this.isDestroyed && !this.bodyEl.contains(e.target) && this.validateBlur(e)) {
96652             this.triggerBlur();
96653         }
96654     },
96655
96656     // private
96657     triggerBlur: function() {
96658         var me = this;
96659         me.mimicing = false;
96660         me.mun(me.doc, 'mousedown', me.mimicBlur, me);
96661         if (me.monitorTab && me.inputEl) {
96662             me.un('specialkey', me.checkTab, me);
96663         }
96664         Ext.form.field.Trigger.superclass.onBlur.call(me);
96665         if (me.bodyEl) {
96666             me.bodyEl.removeCls(me.wrapFocusCls);
96667         }
96668     },
96669
96670     beforeBlur: Ext.emptyFn,
96671
96672     // private
96673     // This should be overridden by any subclass that needs to check whether or not the field can be blurred.
96674     validateBlur: function(e) {
96675         return true;
96676     },
96677
96678     // private
96679     // process clicks upon triggers.
96680     // determine which trigger index, and dispatch to the appropriate click handler
96681     onTriggerWrapClick: function(e) {
96682         var me = this,
96683             t = e && e.getTarget('.' + Ext.baseCSSPrefix + 'form-trigger', null),
96684             match = t && t.className.match(me.triggerIndexRe),
96685             idx,
96686             triggerClickMethod;
96687
96688         if (match && !me.readOnly) {
96689             idx = parseInt(match[1], 10);
96690             triggerClickMethod = me['onTrigger' + (idx + 1) + 'Click'] || me.onTriggerClick;
96691             if (triggerClickMethod) {
96692                 triggerClickMethod.call(me, e);
96693             }
96694         }
96695     },
96696
96697     /**
96698      * The function that should handle the trigger's click event.  This method does nothing by default
96699      * until overridden by an implementing function.  See Ext.form.field.ComboBox and Ext.form.field.Date for
96700      * sample implementations.
96701      * @method
96702      * @param {Ext.EventObject} e
96703      */
96704     onTriggerClick: Ext.emptyFn
96705
96706     /**
96707      * @cfg {Boolean} grow @hide
96708      */
96709     /**
96710      * @cfg {Number} growMin @hide
96711      */
96712     /**
96713      * @cfg {Number} growMax @hide
96714      */
96715 });
96716
96717 /**
96718  * @class Ext.form.field.Picker
96719  * @extends Ext.form.field.Trigger
96720  * <p>An abstract class for fields that have a single trigger which opens a "picker" popup below
96721  * the field, e.g. a combobox menu list or a date picker. It provides a base implementation for
96722  * toggling the picker's visibility when the trigger is clicked, as well as keyboard navigation
96723  * and some basic events. Sizing and alignment of the picker can be controlled via the {@link #matchFieldWidth}
96724  * and {@link #pickerAlign}/{@link #pickerOffset} config properties respectively.</p>
96725  * <p>You would not normally use this class directly, but instead use it as the parent class for
96726  * a specific picker field implementation. Subclasses must implement the {@link #createPicker} method
96727  * to create a picker component appropriate for the field.</p>
96728  *
96729  * @xtype pickerfield
96730  * @constructor
96731  * Create a new picker field
96732  * @param {Object} config
96733  */
96734 Ext.define('Ext.form.field.Picker', {
96735     extend: 'Ext.form.field.Trigger',
96736     alias: 'widget.pickerfield',
96737     alternateClassName: 'Ext.form.Picker',
96738     requires: ['Ext.util.KeyNav'],
96739
96740     /**
96741      * @cfg {Boolean} matchFieldWidth
96742      * Whether the picker dropdown's width should be explicitly set to match the width of the field.
96743      * Defaults to <tt>true</tt>.
96744      */
96745     matchFieldWidth: true,
96746
96747     /**
96748      * @cfg {String} pickerAlign
96749      * The {@link Ext.core.Element#alignTo alignment position} with which to align the picker. Defaults
96750      * to <tt>"tl-bl?"</tt>
96751      */
96752     pickerAlign: 'tl-bl?',
96753
96754     /**
96755      * @cfg {Array} pickerOffset
96756      * An offset [x,y] to use in addition to the {@link #pickerAlign} when positioning the picker.
96757      * Defaults to undefined.
96758      */
96759
96760     /**
96761      * @cfg {String} openCls
96762      * A class to be added to the field's {@link #bodyEl} element when the picker is opened. Defaults
96763      * to 'x-pickerfield-open'.
96764      */
96765     openCls: Ext.baseCSSPrefix + 'pickerfield-open',
96766
96767     /**
96768      * @property isExpanded
96769      * @type Boolean
96770      * True if the picker is currently expanded, false if not.
96771      */
96772
96773     /**
96774      * @cfg {Boolean} editable <tt>false</tt> to prevent the user from typing text directly into the field;
96775      * the field can only have its value set via selecting a value from the picker. In this state, the picker
96776      * can also be opened by clicking directly on the input field itself.
96777      * (defaults to <tt>true</tt>).
96778      */
96779     editable: true,
96780
96781
96782     initComponent: function() {
96783         this.callParent();
96784
96785         // Custom events
96786         this.addEvents(
96787             /**
96788              * @event expand
96789              * Fires when the field's picker is expanded.
96790              * @param {Ext.form.field.Picker} field This field instance
96791              */
96792             'expand',
96793             /**
96794              * @event collapse
96795              * Fires when the field's picker is collapsed.
96796              * @param {Ext.form.field.Picker} field This field instance
96797              */
96798             'collapse',
96799             /**
96800              * @event select
96801              * Fires when a value is selected via the picker.
96802              * @param {Ext.form.field.Picker} field This field instance
96803              * @param {Mixed} value The value that was selected. The exact type of this value is dependent on
96804              * the individual field and picker implementations.
96805              */
96806             'select'
96807         );
96808     },
96809
96810
96811     initEvents: function() {
96812         var me = this;
96813         me.callParent();
96814
96815         // Add handlers for keys to expand/collapse the picker
96816         me.keyNav = Ext.create('Ext.util.KeyNav', me.inputEl, {
96817             down: function() {
96818                 if (!me.isExpanded) {
96819                     // Don't call expand() directly as there may be additional processing involved before
96820                     // expanding, e.g. in the case of a ComboBox query.
96821                     me.onTriggerClick();
96822                 }
96823             },
96824             esc: me.collapse,
96825             scope: me,
96826             forceKeyDown: true
96827         });
96828
96829         // Non-editable allows opening the picker by clicking the field
96830         if (!me.editable) {
96831             me.mon(me.inputEl, 'click', me.onTriggerClick, me);
96832         }
96833
96834         // Disable native browser autocomplete
96835         if (Ext.isGecko) {
96836             me.inputEl.dom.setAttribute('autocomplete', 'off');
96837         }
96838     },
96839
96840
96841     /**
96842      * Expand this field's picker dropdown.
96843      */
96844     expand: function() {
96845         var me = this,
96846             bodyEl, picker, collapseIf;
96847
96848         if (me.rendered && !me.isExpanded && !me.isDestroyed) {
96849             bodyEl = me.bodyEl;
96850             picker = me.getPicker();
96851             collapseIf = me.collapseIf;
96852
96853             // show the picker and set isExpanded flag
96854             picker.show();
96855             me.isExpanded = true;
96856             me.alignPicker();
96857             bodyEl.addCls(me.openCls);
96858
96859             // monitor clicking and mousewheel
96860             me.mon(Ext.getDoc(), {
96861                 mousewheel: collapseIf,
96862                 mousedown: collapseIf,
96863                 scope: me
96864             });
96865
96866             me.fireEvent('expand', me);
96867             me.onExpand();
96868         }
96869     },
96870
96871     onExpand: Ext.emptyFn,
96872
96873     /**
96874      * @protected
96875      * Aligns the picker to the
96876      */
96877     alignPicker: function() {
96878         var me = this,
96879             picker, isAbove,
96880             aboveSfx = '-above';
96881
96882         if (this.isExpanded) {
96883             picker = me.getPicker();
96884             if (me.matchFieldWidth) {
96885                 // Auto the height (it will be constrained by min and max width) unless there are no records to display.
96886                 picker.setSize(me.bodyEl.getWidth(), picker.store && picker.store.getCount() ? null : 0);
96887             }
96888             if (picker.isFloating()) {
96889                 picker.alignTo(me.inputEl, me.pickerAlign, me.pickerOffset);
96890
96891                 // add the {openCls}-above class if the picker was aligned above
96892                 // the field due to hitting the bottom of the viewport
96893                 isAbove = picker.el.getY() < me.inputEl.getY();
96894                 me.bodyEl[isAbove ? 'addCls' : 'removeCls'](me.openCls + aboveSfx);
96895                 picker.el[isAbove ? 'addCls' : 'removeCls'](picker.baseCls + aboveSfx);
96896             }
96897         }
96898     },
96899
96900     /**
96901      * Collapse this field's picker dropdown.
96902      */
96903     collapse: function() {
96904         if (this.isExpanded && !this.isDestroyed) {
96905             var me = this,
96906                 openCls = me.openCls,
96907                 picker = me.picker,
96908                 doc = Ext.getDoc(),
96909                 collapseIf = me.collapseIf,
96910                 aboveSfx = '-above';
96911
96912             // hide the picker and set isExpanded flag
96913             picker.hide();
96914             me.isExpanded = false;
96915
96916             // remove the openCls
96917             me.bodyEl.removeCls([openCls, openCls + aboveSfx]);
96918             picker.el.removeCls(picker.baseCls + aboveSfx);
96919
96920             // remove event listeners
96921             doc.un('mousewheel', collapseIf, me);
96922             doc.un('mousedown', collapseIf, me);
96923
96924             me.fireEvent('collapse', me);
96925             me.onCollapse();
96926         }
96927     },
96928
96929     onCollapse: Ext.emptyFn,
96930
96931
96932     /**
96933      * @private
96934      * Runs on mousewheel and mousedown of doc to check to see if we should collapse the picker
96935      */
96936     collapseIf: function(e) {
96937         var me = this;
96938         if (!me.isDestroyed && !e.within(me.bodyEl, false, true) && !e.within(me.picker.el, false, true)) {
96939             me.collapse();
96940         }
96941     },
96942
96943     /**
96944      * Return a reference to the picker component for this field, creating it if necessary by
96945      * calling {@link #createPicker}.
96946      * @return {Ext.Component} The picker component
96947      */
96948     getPicker: function() {
96949         var me = this;
96950         return me.picker || (me.picker = me.createPicker());
96951     },
96952
96953     /**
96954      * Create and return the component to be used as this field's picker. Must be implemented
96955      * by subclasses of Picker.
96956      * @return {Ext.Component} The picker component
96957      */
96958     createPicker: Ext.emptyFn,
96959
96960     /**
96961      * Handles the trigger click; by default toggles between expanding and collapsing the
96962      * picker component.
96963      */
96964     onTriggerClick: function() {
96965         var me = this;
96966         if (!me.readOnly && !me.disabled) {
96967             if (me.isExpanded) {
96968                 me.collapse();
96969             } else {
96970                 me.expand();
96971             }
96972             me.inputEl.focus();
96973         }
96974     },
96975
96976     mimicBlur: function(e) {
96977         var me = this,
96978             picker = me.picker;
96979         // ignore mousedown events within the picker element
96980         if (!picker || !e.within(picker.el, false, true)) {
96981             me.callParent(arguments);
96982         }
96983     },
96984
96985     onDestroy : function(){
96986         var me = this;
96987         Ext.destroy(me.picker, me.keyNav);
96988         me.callParent();
96989     }
96990
96991 });
96992
96993
96994 /**
96995  * @class Ext.form.field.Spinner
96996  * @extends Ext.form.field.Trigger
96997  * <p>A field with a pair of up/down spinner buttons. This class is not normally instantiated directly,
96998  * instead it is subclassed and the {@link #onSpinUp} and {@link #onSpinDown} methods are implemented
96999  * to handle when the buttons are clicked. A good example of this is the {@link Ext.form.field.Number} field
97000  * which uses the spinner to increment and decrement the field's value by its {@link Ext.form.field.Number#step step}
97001  * config value.</p>
97002  * {@img Ext.form.field.Spinner/Ext.form.field.Spinner.png Ext.form.field.Spinner field}
97003  * For example:
97004      Ext.define('Ext.ux.CustomSpinner', {
97005         extend: 'Ext.form.field.Spinner',
97006         alias: 'widget.customspinner',
97007         
97008         // override onSpinUp (using step isn't neccessary)
97009         onSpinUp: function() {
97010             var me = this;
97011             if (!me.readOnly) {
97012                 var val = me.step; // set the default value to the step value
97013                 if(me.getValue() !== '') {
97014                     val = parseInt(me.getValue().slice(0, -5)); // gets rid of " Pack"
97015                 }                          
97016                 me.setValue((val + me.step) + ' Pack');
97017             }
97018         },
97019         
97020         // override onSpinDown
97021         onSpinDown: function() {
97022             var me = this;
97023             if (!me.readOnly) {
97024                 if(me.getValue() !== '') {
97025                     val = parseInt(me.getValue().slice(0, -5)); // gets rid of " Pack"
97026                 }            
97027                 me.setValue((val - me.step) + ' Pack');
97028             }
97029         }
97030     });
97031     
97032     Ext.create('Ext.form.FormPanel', {
97033         title: 'Form with SpinnerField',
97034         bodyPadding: 5,
97035         width: 350,
97036         renderTo: Ext.getBody(),
97037         items:[{
97038             xtype: 'customspinner',
97039             fieldLabel: 'How Much Beer?',
97040             step: 6
97041         }]
97042     });
97043  * <p>By default, pressing the up and down arrow keys will also trigger the onSpinUp and onSpinDown methods;
97044  * to prevent this, set <tt>{@link #keyNavEnabled} = false</tt>.</p>
97045  *
97046  * @constructor
97047  * Creates a new Spinner field
97048  * @param {Object} config Configuration options
97049  * @xtype spinnerfield
97050  */
97051 Ext.define('Ext.form.field.Spinner', {
97052     extend: 'Ext.form.field.Trigger',
97053     alias: 'widget.spinnerfield',
97054     alternateClassName: 'Ext.form.Spinner',
97055     requires: ['Ext.util.KeyNav'],
97056
97057     trigger1Cls: Ext.baseCSSPrefix + 'form-spinner-up',
97058     trigger2Cls: Ext.baseCSSPrefix + 'form-spinner-down',
97059
97060     /**
97061      * @cfg {Boolean} spinUpEnabled
97062      * Specifies whether the up spinner button is enabled. Defaults to <tt>true</tt>. To change this
97063      * after the component is created, use the {@link #setSpinUpEnabled} method.
97064      */
97065     spinUpEnabled: true,
97066
97067     /**
97068      * @cfg {Boolean} spinDownEnabled
97069      * Specifies whether the down spinner button is enabled. Defaults to <tt>true</tt>. To change this
97070      * after the component is created, use the {@link #setSpinDownEnabled} method.
97071      */
97072     spinDownEnabled: true,
97073
97074     /**
97075      * @cfg {Boolean} keyNavEnabled
97076      * Specifies whether the up and down arrow keys should trigger spinning up and down.
97077      * Defaults to <tt>true</tt>.
97078      */
97079     keyNavEnabled: true,
97080
97081     /**
97082      * @cfg {Boolean} mouseWheelEnabled
97083      * Specifies whether the mouse wheel should trigger spinning up and down while the field has
97084      * focus. Defaults to <tt>true</tt>.
97085      */
97086     mouseWheelEnabled: true,
97087
97088     /**
97089      * @cfg {Boolean} repeatTriggerClick Whether a {@link Ext.util.ClickRepeater click repeater} should be
97090      * attached to the spinner buttons. Defaults to <tt>true</tt>.
97091      */
97092     repeatTriggerClick: true,
97093
97094     /**
97095      * This method is called when the spinner up button is clicked, or when the up arrow key is pressed
97096      * if {@link #keyNavEnabled} is <tt>true</tt>. Must be implemented by subclasses.
97097      */
97098     onSpinUp: Ext.emptyFn,
97099
97100     /**
97101      * This method is called when the spinner down button is clicked, or when the down arrow key is pressed
97102      * if {@link #keyNavEnabled} is <tt>true</tt>. Must be implemented by subclasses.
97103      */
97104     onSpinDown: Ext.emptyFn,
97105
97106     initComponent: function() {
97107         this.callParent();
97108
97109         this.addEvents(
97110             /**
97111              * @event spin
97112              * Fires when the spinner is made to spin up or down.
97113              * @param {Ext.form.field.Spinner} this
97114              * @param {String} direction Either 'up' if spinning up, or 'down' if spinning down.
97115              */
97116             'spin',
97117
97118             /**
97119              * @event spinup
97120              * Fires when the spinner is made to spin up.
97121              * @param {Ext.form.field.Spinner} this
97122              */
97123             'spinup',
97124
97125             /**
97126              * @event spindown
97127              * Fires when the spinner is made to spin down.
97128              * @param {Ext.form.field.Spinner} this
97129              */
97130             'spindown'
97131         );
97132     },
97133
97134     /**
97135      * @private override
97136      */
97137     onRender: function() {
97138         var me = this,
97139             triggers;
97140
97141         me.callParent(arguments);
97142         triggers = me.triggerEl;
97143
97144         /**
97145          * @property spinUpEl
97146          * @type Ext.core.Element
97147          * The spinner up button element
97148          */
97149         me.spinUpEl = triggers.item(0);
97150         /**
97151          * @property spinDownEl
97152          * @type Ext.core.Element
97153          * The spinner down button element
97154          */
97155         me.spinDownEl = triggers.item(1);
97156
97157         // Set initial enabled/disabled states
97158         me.setSpinUpEnabled(me.spinUpEnabled);
97159         me.setSpinDownEnabled(me.spinDownEnabled);
97160
97161         // Init up/down arrow keys
97162         if (me.keyNavEnabled) {
97163             me.spinnerKeyNav = Ext.create('Ext.util.KeyNav', me.inputEl, {
97164                 scope: me,
97165                 up: me.spinUp,
97166                 down: me.spinDown
97167             });
97168         }
97169
97170         // Init mouse wheel
97171         if (me.mouseWheelEnabled) {
97172             me.mon(me.bodyEl, 'mousewheel', me.onMouseWheel, me);
97173         }
97174     },
97175
97176     /**
97177      * @private override
97178      * Since the triggers are stacked, only measure the width of one of them.
97179      */
97180     getTriggerWidth: function() {
97181         return this.hideTrigger || this.readOnly ? 0 : this.spinUpEl.getWidth() + this.triggerWrap.getFrameWidth('lr');
97182     },
97183
97184     /**
97185      * @private Handles the spinner up button clicks.
97186      */
97187     onTrigger1Click: function() {
97188         this.spinUp();
97189     },
97190
97191     /**
97192      * @private Handles the spinner down button clicks.
97193      */
97194     onTrigger2Click: function() {
97195         this.spinDown();
97196     },
97197
97198     /**
97199      * Triggers the spinner to step up; fires the {@link #spin} and {@link #spinup} events and calls the
97200      * {@link #onSpinUp} method. Does nothing if the field is {@link #disabled} or if {@link #spinUpEnabled}
97201      * is false.
97202      */
97203     spinUp: function() {
97204         var me = this;
97205         if (me.spinUpEnabled && !me.disabled) {
97206             me.fireEvent('spin', me, 'up');
97207             me.fireEvent('spinup', me);
97208             me.onSpinUp();
97209         }
97210     },
97211
97212     /**
97213      * Triggers the spinner to step down; fires the {@link #spin} and {@link #spindown} events and calls the
97214      * {@link #onSpinDown} method. Does nothing if the field is {@link #disabled} or if {@link #spinDownEnabled}
97215      * is false.
97216      */
97217     spinDown: function() {
97218         var me = this;
97219         if (me.spinDownEnabled && !me.disabled) {
97220             me.fireEvent('spin', me, 'down');
97221             me.fireEvent('spindown', me);
97222             me.onSpinDown();
97223         }
97224     },
97225
97226     /**
97227      * Sets whether the spinner up button is enabled.
97228      * @param {Boolean} enabled true to enable the button, false to disable it.
97229      */
97230     setSpinUpEnabled: function(enabled) {
97231         var me = this,
97232             wasEnabled = me.spinUpEnabled;
97233         me.spinUpEnabled = enabled;
97234         if (wasEnabled !== enabled && me.rendered) {
97235             me.spinUpEl[enabled ? 'removeCls' : 'addCls'](me.trigger1Cls + '-disabled');
97236         }
97237     },
97238
97239     /**
97240      * Sets whether the spinner down button is enabled.
97241      * @param {Boolean} enabled true to enable the button, false to disable it.
97242      */
97243     setSpinDownEnabled: function(enabled) {
97244         var me = this,
97245             wasEnabled = me.spinDownEnabled;
97246         me.spinDownEnabled = enabled;
97247         if (wasEnabled !== enabled && me.rendered) {
97248             me.spinDownEl[enabled ? 'removeCls' : 'addCls'](me.trigger2Cls + '-disabled');
97249         }
97250     },
97251
97252     /**
97253      * @private
97254      * Handles mousewheel events on the field
97255      */
97256     onMouseWheel: function(e) {
97257         var me = this,
97258             delta;
97259         if (me.hasFocus) {
97260             delta = e.getWheelDelta();
97261             if (delta > 0) {
97262                 me.spinUp();
97263             }
97264             else if (delta < 0) {
97265                 me.spinDown();
97266             }
97267             e.stopEvent();
97268         }
97269     },
97270
97271     onDestroy: function() {
97272         Ext.destroyMembers(this, 'spinnerKeyNav', 'spinUpEl', 'spinDownEl');
97273         this.callParent();
97274     }
97275
97276 });
97277 /**
97278  * @class Ext.form.field.Number
97279  * @extends Ext.form.field.Spinner
97280
97281 A numeric text field that provides automatic keystroke filtering to disallow non-numeric characters,
97282 and numeric validation to limit the value to a range of valid numbers. The range of acceptable number
97283 values can be controlled by setting the {@link #minValue} and {@link #maxValue} configs, and fractional
97284 decimals can be disallowed by setting {@link #allowDecimals} to `false`.
97285
97286 By default, the number field is also rendered with a set of up/down spinner buttons and has
97287 up/down arrow key and mouse wheel event listeners attached for incrementing/decrementing the value by the
97288 {@link #step} value. To hide the spinner buttons set `{@link #hideTrigger hideTrigger}:true`; to disable the arrow key
97289 and mouse wheel handlers set `{@link #keyNavEnabled keyNavEnabled}:false` and
97290 `{@link #mouseWheelEnabled mouseWheelEnabled}:false`. See the example below.
97291
97292 #Example usage:#
97293 {@img Ext.form.Number/Ext.form.Number1.png Ext.form.Number component}
97294     Ext.create('Ext.form.Panel', {
97295         title: 'On The Wall',
97296         width: 300,
97297         bodyPadding: 10,
97298         renderTo: Ext.getBody(),
97299         items: [{
97300             xtype: 'numberfield',
97301             anchor: '100%',
97302             name: 'bottles',
97303             fieldLabel: 'Bottles of Beer',
97304             value: 99,
97305             maxValue: 99,
97306             minValue: 0
97307         }],
97308         buttons: [{
97309             text: 'Take one down, pass it around',
97310             handler: function() {
97311                 this.up('form').down('[name=bottles]').spinDown();
97312             }
97313         }]
97314     });
97315
97316 #Removing UI Enhancements#
97317 {@img Ext.form.Number/Ext.form.Number2.png Ext.form.Number component}
97318     Ext.create('Ext.form.Panel', {
97319         title: 'Personal Info',
97320         width: 300,
97321         bodyPadding: 10,
97322         renderTo: Ext.getBody(),        
97323         items: [{
97324             xtype: 'numberfield',
97325             anchor: '100%',
97326             name: 'age',
97327             fieldLabel: 'Age',
97328             minValue: 0, //prevents negative numbers
97329     
97330             // Remove spinner buttons, and arrow key and mouse wheel listeners
97331             hideTrigger: true,
97332             keyNavEnabled: false,
97333             mouseWheelEnabled: false
97334         }]
97335     });
97336
97337 #Using Step#
97338     Ext.create('Ext.form.Panel', {
97339         renderTo: Ext.getBody(),
97340         title: 'Step',
97341         width: 300,
97342         bodyPadding: 10,
97343         items: [{
97344             xtype: 'numberfield',
97345             anchor: '100%',
97346             name: 'evens',
97347             fieldLabel: 'Even Numbers',
97348
97349             // Set step so it skips every other number
97350             step: 2,
97351             value: 0,
97352
97353             // Add change handler to force user-entered numbers to evens
97354             listeners: {
97355                 change: function(field, value) {
97356                     value = parseInt(value, 10);
97357                     field.setValue(value + value % 2);
97358                 }
97359             }
97360         }]
97361     });
97362
97363
97364  * @constructor
97365  * Creates a new Number field
97366  * @param {Object} config Configuration options
97367  *
97368  * @xtype numberfield
97369  * @markdown
97370  * @docauthor Jason Johnston <jason@sencha.com>
97371  */
97372 Ext.define('Ext.form.field.Number', {
97373     extend:'Ext.form.field.Spinner',
97374     alias: 'widget.numberfield',
97375     alternateClassName: ['Ext.form.NumberField', 'Ext.form.Number'],
97376
97377     /**
97378      * @cfg {RegExp} stripCharsRe @hide
97379      */
97380     /**
97381      * @cfg {RegExp} maskRe @hide
97382      */
97383
97384     /**
97385      * @cfg {Boolean} allowDecimals False to disallow decimal values (defaults to true)
97386      */
97387     allowDecimals : true,
97388
97389     /**
97390      * @cfg {String} decimalSeparator Character(s) to allow as the decimal separator (defaults to '.')
97391      */
97392     decimalSeparator : '.',
97393
97394     /**
97395      * @cfg {Number} decimalPrecision The maximum precision to display after the decimal separator (defaults to 2)
97396      */
97397     decimalPrecision : 2,
97398
97399     /**
97400      * @cfg {Number} minValue The minimum allowed value (defaults to Number.NEGATIVE_INFINITY). Will be used by
97401      * the field's validation logic, and for
97402      * {@link Ext.form.field.Spinner#setSpinUpEnabled enabling/disabling the down spinner button}.
97403      */
97404     minValue: Number.NEGATIVE_INFINITY,
97405
97406     /**
97407      * @cfg {Number} maxValue The maximum allowed value (defaults to Number.MAX_VALUE). Will be used by
97408      * the field's validation logic, and for
97409      * {@link Ext.form.field.Spinner#setSpinUpEnabled enabling/disabling the up spinner button}.
97410      */
97411     maxValue: Number.MAX_VALUE,
97412
97413     /**
97414      * @cfg {Number} step Specifies a numeric interval by which the field's value will be incremented or
97415      * decremented when the user invokes the spinner. Defaults to <tt>1</tt>.
97416      */
97417     step: 1,
97418
97419     /**
97420      * @cfg {String} minText Error text to display if the minimum value validation fails (defaults to 'The minimum
97421      * value for this field is {minValue}')
97422      */
97423     minText : 'The minimum value for this field is {0}',
97424
97425     /**
97426      * @cfg {String} maxText Error text to display if the maximum value validation fails (defaults to 'The maximum
97427      * value for this field is {maxValue}')
97428      */
97429     maxText : 'The maximum value for this field is {0}',
97430
97431     /**
97432      * @cfg {String} nanText Error text to display if the value is not a valid number.  For example, this can happen
97433      * if a valid character like '.' or '-' is left in the field with no number (defaults to '{value} is not a valid number')
97434      */
97435     nanText : '{0} is not a valid number',
97436
97437     /**
97438      * @cfg {String} negativeText Error text to display if the value is negative and {@link #minValue} is set to
97439      * <tt>0</tt>. This is used instead of the {@link #minText} in that circumstance only.
97440      */
97441     negativeText : 'The value cannot be negative',
97442
97443     /**
97444      * @cfg {String} baseChars The base set of characters to evaluate as valid numbers (defaults to '0123456789').
97445      */
97446     baseChars : '0123456789',
97447
97448     /**
97449      * @cfg {Boolean} autoStripChars True to automatically strip not allowed characters from the field. Defaults to <tt>false</tt>
97450      */
97451     autoStripChars: false,
97452
97453     initComponent: function() {
97454         var me = this,
97455             allowed;
97456
97457         this.callParent();
97458
97459         me.setMinValue(me.minValue);
97460         me.setMaxValue(me.maxValue);
97461
97462         // Build regexes for masking and stripping based on the configured options
97463         allowed = me.baseChars + '';
97464         if (me.allowDecimals) {
97465             allowed += me.decimalSeparator;
97466         }
97467         if (me.minValue < 0) {
97468             allowed += '-';
97469         }
97470         allowed = Ext.String.escapeRegex(allowed);
97471         me.maskRe = new RegExp('[' + allowed + ']');
97472         if (me.autoStripChars) {
97473             me.stripCharsRe = new RegExp('[^' + allowed + ']', 'gi');
97474         }
97475     },
97476
97477     /**
97478      * Runs all of Number's validations and returns an array of any errors. Note that this first
97479      * runs Text's validations, so the returned array is an amalgamation of all field errors.
97480      * The additional validations run test that the value is a number, and that it is within the
97481      * configured min and max values.
97482      * @param {Mixed} value The value to get errors for (defaults to the current field value)
97483      * @return {Array} All validation errors for this field
97484      */
97485     getErrors: function(value) {
97486         var me = this,
97487             errors = me.callParent(arguments),
97488             format = Ext.String.format,
97489             num;
97490
97491         value = Ext.isDefined(value) ? value : this.processRawValue(this.getRawValue());
97492
97493         if (value.length < 1) { // if it's blank and textfield didn't flag it then it's valid
97494              return errors;
97495         }
97496
97497         value = String(value).replace(me.decimalSeparator, '.');
97498
97499         if(isNaN(value)){
97500             errors.push(format(me.nanText, value));
97501         }
97502
97503         num = me.parseValue(value);
97504
97505         if (me.minValue === 0 && num < 0) {
97506             errors.push(this.negativeText);
97507         }
97508         else if (num < me.minValue) {
97509             errors.push(format(me.minText, me.minValue));
97510         }
97511
97512         if (num > me.maxValue) {
97513             errors.push(format(me.maxText, me.maxValue));
97514         }
97515
97516
97517         return errors;
97518     },
97519
97520     rawToValue: function(rawValue) {
97521         return this.fixPrecision(this.parseValue(rawValue)) || rawValue || null;
97522     },
97523
97524     valueToRaw: function(value) {
97525         var me = this,
97526             decimalSeparator = me.decimalSeparator;
97527         value = me.parseValue(value);
97528         value = me.fixPrecision(value);
97529         value = Ext.isNumber(value) ? value : parseFloat(String(value).replace(decimalSeparator, '.'));
97530         value = isNaN(value) ? '' : String(value).replace('.', decimalSeparator);
97531         return value;
97532     },
97533
97534     onChange: function() {
97535         var me = this,
97536             value = me.getValue(),
97537             valueIsNull = value === null;
97538
97539         me.callParent(arguments);
97540
97541         // Update the spinner buttons
97542         me.setSpinUpEnabled(valueIsNull || value < me.maxValue);
97543         me.setSpinDownEnabled(valueIsNull || value > me.minValue);
97544     },
97545
97546     /**
97547      * Replaces any existing {@link #minValue} with the new value.
97548      * @param {Number} value The minimum value
97549      */
97550     setMinValue : function(value) {
97551         this.minValue = Ext.Number.from(value, Number.NEGATIVE_INFINITY);
97552     },
97553
97554     /**
97555      * Replaces any existing {@link #maxValue} with the new value.
97556      * @param {Number} value The maximum value
97557      */
97558     setMaxValue: function(value) {
97559         this.maxValue = Ext.Number.from(value, Number.MAX_VALUE);
97560     },
97561
97562     // private
97563     parseValue : function(value) {
97564         value = parseFloat(String(value).replace(this.decimalSeparator, '.'));
97565         return isNaN(value) ? null : value;
97566     },
97567
97568     /**
97569      * @private
97570      *
97571      */
97572     fixPrecision : function(value) {
97573         var me = this,
97574             nan = isNaN(value),
97575             precision = me.decimalPrecision;
97576
97577         if (nan || !value) {
97578             return nan ? '' : value;
97579         } else if (!me.allowDecimals || precision <= 0) {
97580             precision = 0;
97581         }
97582
97583         return parseFloat(Ext.Number.toFixed(parseFloat(value), precision));
97584     },
97585
97586     beforeBlur : function() {
97587         var me = this,
97588             v = me.parseValue(me.getRawValue());
97589
97590         if (!Ext.isEmpty(v)) {
97591             me.setValue(v);
97592         }
97593     },
97594
97595     onSpinUp: function() {
97596         var me = this;
97597         if (!me.readOnly) {
97598             me.setValue(Ext.Number.constrain(me.getValue() + me.step, me.minValue, me.maxValue));
97599         }
97600     },
97601
97602     onSpinDown: function() {
97603         var me = this;
97604         if (!me.readOnly) {
97605             me.setValue(Ext.Number.constrain(me.getValue() - me.step, me.minValue, me.maxValue));
97606         }
97607     }
97608 });
97609
97610 /**
97611  * @class Ext.toolbar.Paging
97612  * @extends Ext.toolbar.Toolbar
97613  * <p>As the amount of records increases, the time required for the browser to render
97614  * them increases. Paging is used to reduce the amount of data exchanged with the client.
97615  * Note: if there are more records/rows than can be viewed in the available screen area, vertical
97616  * scrollbars will be added.</p>
97617  * <p>Paging is typically handled on the server side (see exception below). The client sends
97618  * parameters to the server side, which the server needs to interpret and then respond with the
97619  * appropriate data.</p>
97620  * <p><b>Ext.toolbar.Paging</b> is a specialized toolbar that is bound to a {@link Ext.data.Store}
97621  * and provides automatic paging control. This Component {@link Ext.data.Store#load load}s blocks
97622  * of data into the <tt>{@link #store}</tt> by passing {@link Ext.data.Store#paramNames paramNames} used for
97623  * paging criteria.</p>
97624  *
97625  * {@img Ext.toolbar.Paging/Ext.toolbar.Paging.png Ext.toolbar.Paging component}
97626  *
97627  * <p>PagingToolbar is typically used as one of the Grid's toolbars:</p>
97628  * <pre><code>
97629  *    var itemsPerPage = 2;   // set the number of items you want per page
97630  *    
97631  *    var store = Ext.create('Ext.data.Store', {
97632  *        id:'simpsonsStore',
97633  *        autoLoad: false,
97634  *        fields:['name', 'email', 'phone'],
97635  *        pageSize: itemsPerPage, // items per page
97636  *        proxy: {
97637  *            type: 'ajax',
97638  *            url: 'pagingstore.js',  // url that will load data with respect to start and limit params
97639  *            reader: {
97640  *                type: 'json',
97641  *                root: 'items',
97642  *                totalProperty: 'total'
97643  *            }
97644  *        }
97645  *    });
97646  *    
97647  *    // specify segment of data you want to load using params
97648  *    store.load({
97649  *        params:{
97650  *            start:0,    
97651  *            limit: itemsPerPage
97652  *        }
97653  *    });
97654  *    
97655  *    Ext.create('Ext.grid.Panel', {
97656  *        title: 'Simpsons',
97657  *        store: store,
97658  *        columns: [
97659  *            {header: 'Name',  dataIndex: 'name'},
97660  *            {header: 'Email', dataIndex: 'email', flex:1},
97661  *            {header: 'Phone', dataIndex: 'phone'}
97662  *        ],
97663  *        width: 400,
97664  *        height: 125,
97665  *        dockedItems: [{
97666  *            xtype: 'pagingtoolbar',
97667  *            store: store,   // same store GridPanel is using
97668  *            dock: 'bottom',
97669  *            displayInfo: true
97670  *        }],
97671  *        renderTo: Ext.getBody()
97672  *    });
97673  * </code></pre>
97674  *
97675  * <p>To use paging, pass the paging requirements to the server when the store is first loaded.</p>
97676  * <pre><code>
97677 store.load({
97678     params: {
97679         // specify params for the first page load if using paging
97680         start: 0,          
97681         limit: myPageSize,
97682         // other params
97683         foo:   'bar'
97684     }
97685 });
97686  * </code></pre>
97687  * 
97688  * <p>If using {@link Ext.data.Store#autoLoad store's autoLoad} configuration:</p>
97689  * <pre><code>
97690 var myStore = new Ext.data.Store({
97691     {@link Ext.data.Store#autoLoad autoLoad}: {start: 0, limit: 25},
97692     ...
97693 });
97694  * </code></pre>
97695  * 
97696  * <p>The packet sent back from the server would have this form:</p>
97697  * <pre><code>
97698 {
97699     "success": true,
97700     "results": 2000, 
97701     "rows": [ // <b>*Note:</b> this must be an Array 
97702         { "id":  1, "name": "Bill", "occupation": "Gardener" },
97703         { "id":  2, "name":  "Ben", "occupation": "Horticulturalist" },
97704         ...
97705         { "id": 25, "name":  "Sue", "occupation": "Botanist" }
97706     ]
97707 }
97708  * </code></pre>
97709  * <p><u>Paging with Local Data</u></p>
97710  * <p>Paging can also be accomplished with local data using extensions:</p>
97711  * <div class="mdetail-params"><ul>
97712  * <li><a href="http://sencha.com/forum/showthread.php?t=71532">Ext.ux.data.PagingStore</a></li>
97713  * <li>Paging Memory Proxy (examples/ux/PagingMemoryProxy.js)</li>
97714  * </ul></div>
97715  * @constructor Create a new PagingToolbar
97716  * @param {Object} config The config object
97717  * @xtype pagingtoolbar
97718  */
97719 Ext.define('Ext.toolbar.Paging', {
97720     extend: 'Ext.toolbar.Toolbar',
97721     alias: 'widget.pagingtoolbar',
97722     alternateClassName: 'Ext.PagingToolbar',
97723     requires: ['Ext.toolbar.TextItem', 'Ext.form.field.Number'],
97724     /**
97725      * @cfg {Ext.data.Store} store
97726      * The {@link Ext.data.Store} the paging toolbar should use as its data source (required).
97727      */
97728     /**
97729      * @cfg {Boolean} displayInfo
97730      * <tt>true</tt> to display the displayMsg (defaults to <tt>false</tt>)
97731      */
97732     displayInfo: false,
97733     /**
97734      * @cfg {Boolean} prependButtons
97735      * <tt>true</tt> to insert any configured <tt>items</tt> <i>before</i> the paging buttons.
97736      * Defaults to <tt>false</tt>.
97737      */
97738     prependButtons: false,
97739     /**
97740      * @cfg {String} displayMsg
97741      * The paging status message to display (defaults to <tt>'Displaying {0} - {1} of {2}'</tt>).
97742      * Note that this string is formatted using the braced numbers <tt>{0}-{2}</tt> as tokens
97743      * that are replaced by the values for start, end and total respectively. These tokens should
97744      * be preserved when overriding this string if showing those values is desired.
97745      */
97746     displayMsg : 'Displaying {0} - {1} of {2}',
97747     /**
97748      * @cfg {String} emptyMsg
97749      * The message to display when no records are found (defaults to 'No data to display')
97750      */
97751     emptyMsg : 'No data to display',
97752     /**
97753      * @cfg {String} beforePageText
97754      * The text displayed before the input item (defaults to <tt>'Page'</tt>).
97755      */
97756     beforePageText : 'Page',
97757     /**
97758      * @cfg {String} afterPageText
97759      * Customizable piece of the default paging text (defaults to <tt>'of {0}'</tt>). Note that
97760      * this string is formatted using <tt>{0}</tt> as a token that is replaced by the number of
97761      * total pages. This token should be preserved when overriding this string if showing the
97762      * total page count is desired.
97763      */
97764     afterPageText : 'of {0}',
97765     /**
97766      * @cfg {String} firstText
97767      * The quicktip text displayed for the first page button (defaults to <tt>'First Page'</tt>).
97768      * <b>Note</b>: quick tips must be initialized for the quicktip to show.
97769      */
97770     firstText : 'First Page',
97771     /**
97772      * @cfg {String} prevText
97773      * The quicktip text displayed for the previous page button (defaults to <tt>'Previous Page'</tt>).
97774      * <b>Note</b>: quick tips must be initialized for the quicktip to show.
97775      */
97776     prevText : 'Previous Page',
97777     /**
97778      * @cfg {String} nextText
97779      * The quicktip text displayed for the next page button (defaults to <tt>'Next Page'</tt>).
97780      * <b>Note</b>: quick tips must be initialized for the quicktip to show.
97781      */
97782     nextText : 'Next Page',
97783     /**
97784      * @cfg {String} lastText
97785      * The quicktip text displayed for the last page button (defaults to <tt>'Last Page'</tt>).
97786      * <b>Note</b>: quick tips must be initialized for the quicktip to show.
97787      */
97788     lastText : 'Last Page',
97789     /**
97790      * @cfg {String} refreshText
97791      * The quicktip text displayed for the Refresh button (defaults to <tt>'Refresh'</tt>).
97792      * <b>Note</b>: quick tips must be initialized for the quicktip to show.
97793      */
97794     refreshText : 'Refresh',
97795     /**
97796      * @cfg {Number} inputItemWidth
97797      * The width in pixels of the input field used to display and change the current page number (defaults to 30).
97798      */
97799     inputItemWidth : 30,
97800     
97801     /**
97802      * Gets the standard paging items in the toolbar
97803      * @private
97804      */
97805     getPagingItems: function() {
97806         var me = this;
97807         
97808         return [{
97809             itemId: 'first',
97810             tooltip: me.firstText,
97811             overflowText: me.firstText,
97812             iconCls: Ext.baseCSSPrefix + 'tbar-page-first',
97813             disabled: true,
97814             handler: me.moveFirst,
97815             scope: me
97816         },{
97817             itemId: 'prev',
97818             tooltip: me.prevText,
97819             overflowText: me.prevText,
97820             iconCls: Ext.baseCSSPrefix + 'tbar-page-prev',
97821             disabled: true,
97822             handler: me.movePrevious,
97823             scope: me
97824         },
97825         '-',
97826         me.beforePageText,
97827         {
97828             xtype: 'numberfield',
97829             itemId: 'inputItem',
97830             name: 'inputItem',
97831             cls: Ext.baseCSSPrefix + 'tbar-page-number',
97832             allowDecimals: false,
97833             minValue: 1,
97834             hideTrigger: true,
97835             enableKeyEvents: true,
97836             selectOnFocus: true,
97837             submitValue: false,
97838             width: me.inputItemWidth,
97839             margins: '-1 2 3 2',
97840             listeners: {
97841                 scope: me,
97842                 keydown: me.onPagingKeyDown,
97843                 blur: me.onPagingBlur
97844             }
97845         },{
97846             xtype: 'tbtext',
97847             itemId: 'afterTextItem',
97848             text: Ext.String.format(me.afterPageText, 1)
97849         },
97850         '-',
97851         {
97852             itemId: 'next',
97853             tooltip: me.nextText,
97854             overflowText: me.nextText,
97855             iconCls: Ext.baseCSSPrefix + 'tbar-page-next',
97856             disabled: true,
97857             handler: me.moveNext,
97858             scope: me
97859         },{
97860             itemId: 'last',
97861             tooltip: me.lastText,
97862             overflowText: me.lastText,
97863             iconCls: Ext.baseCSSPrefix + 'tbar-page-last',
97864             disabled: true,
97865             handler: me.moveLast,
97866             scope: me
97867         },
97868         '-',
97869         {
97870             itemId: 'refresh',
97871             tooltip: me.refreshText,
97872             overflowText: me.refreshText,
97873             iconCls: Ext.baseCSSPrefix + 'tbar-loading',
97874             handler: me.doRefresh,
97875             scope: me
97876         }];
97877     },
97878
97879     initComponent : function(){
97880         var me = this,
97881             pagingItems = me.getPagingItems(),
97882             userItems   = me.items || me.buttons || [];
97883             
97884         if (me.prependButtons) {
97885             me.items = userItems.concat(pagingItems);
97886         } else {
97887             me.items = pagingItems.concat(userItems);
97888         }
97889         delete me.buttons;
97890         
97891         if (me.displayInfo) {
97892             me.items.push('->');
97893             me.items.push({xtype: 'tbtext', itemId: 'displayItem'});
97894         }
97895         
97896         me.callParent();
97897         
97898         me.addEvents(
97899             /**
97900              * @event change
97901              * Fires after the active page has been changed.
97902              * @param {Ext.toolbar.Paging} this
97903              * @param {Object} pageData An object that has these properties:<ul>
97904              * <li><code>total</code> : Number <div class="sub-desc">The total number of records in the dataset as
97905              * returned by the server</div></li>
97906              * <li><code>currentPage</code> : Number <div class="sub-desc">The current page number</div></li>
97907              * <li><code>pageCount</code> : Number <div class="sub-desc">The total number of pages (calculated from
97908              * the total number of records in the dataset as returned by the server and the current {@link #pageSize})</div></li>
97909              * <li><code>toRecord</code> : Number <div class="sub-desc">The starting record index for the current page</div></li>
97910              * <li><code>fromRecord</code> : Number <div class="sub-desc">The ending record index for the current page</div></li>
97911              * </ul>
97912              */
97913             'change',
97914             /**
97915              * @event beforechange
97916              * Fires just before the active page is changed.
97917              * Return false to prevent the active page from being changed.
97918              * @param {Ext.toolbar.Paging} this
97919              * @param {Number} page The page number that will be loaded on change 
97920              */
97921             'beforechange'
97922         );
97923         me.on('afterlayout', me.onLoad, me, {single: true});
97924
97925         me.bindStore(me.store, true);
97926     },
97927     // private
97928     updateInfo : function(){
97929         var me = this,
97930             displayItem = me.child('#displayItem'),
97931             store = me.store,
97932             pageData = me.getPageData(),
97933             count, msg;
97934
97935         if (displayItem) {
97936             count = store.getCount();
97937             if (count === 0) {
97938                 msg = me.emptyMsg;
97939             } else {
97940                 msg = Ext.String.format(
97941                     me.displayMsg,
97942                     pageData.fromRecord,
97943                     pageData.toRecord,
97944                     pageData.total
97945                 );
97946             }
97947             displayItem.setText(msg);
97948             me.doComponentLayout();
97949         }
97950     },
97951
97952     // private
97953     onLoad : function(){
97954         var me = this,
97955             pageData,
97956             currPage,
97957             pageCount,
97958             afterText;
97959             
97960         if (!me.rendered) {
97961             return;
97962         }
97963
97964         pageData = me.getPageData();
97965         currPage = pageData.currentPage;
97966         pageCount = pageData.pageCount;
97967         afterText = Ext.String.format(me.afterPageText, isNaN(pageCount) ? 1 : pageCount);
97968
97969         me.child('#afterTextItem').setText(afterText);
97970         me.child('#inputItem').setValue(currPage);
97971         me.child('#first').setDisabled(currPage === 1);
97972         me.child('#prev').setDisabled(currPage === 1);
97973         me.child('#next').setDisabled(currPage === pageCount);
97974         me.child('#last').setDisabled(currPage === pageCount);
97975         me.child('#refresh').enable();
97976         me.updateInfo();
97977         me.fireEvent('change', me, pageData);
97978     },
97979
97980     // private
97981     getPageData : function(){
97982         var store = this.store,
97983             totalCount = store.getTotalCount();
97984             
97985         return {
97986             total : totalCount,
97987             currentPage : store.currentPage,
97988             pageCount: Math.ceil(totalCount / store.pageSize),
97989             //pageCount :  store.getPageCount(),
97990             fromRecord: ((store.currentPage - 1) * store.pageSize) + 1,
97991             toRecord: Math.min(store.currentPage * store.pageSize, totalCount)
97992             
97993         };
97994     },
97995
97996     // private
97997     onLoadError : function(){
97998         if (!this.rendered) {
97999             return;
98000         }
98001         this.child('#refresh').enable();
98002     },
98003
98004     // private
98005     readPageFromInput : function(pageData){
98006         var v = this.child('#inputItem').getValue(),
98007             pageNum = parseInt(v, 10);
98008             
98009         if (!v || isNaN(pageNum)) {
98010             this.child('#inputItem').setValue(pageData.currentPage);
98011             return false;
98012         }
98013         return pageNum;
98014     },
98015
98016     onPagingFocus : function(){
98017         this.child('#inputItem').select();
98018     },
98019
98020     //private
98021     onPagingBlur : function(e){
98022         var curPage = this.getPageData().currentPage;
98023         this.child('#inputItem').setValue(curPage);
98024     },
98025
98026     // private
98027     onPagingKeyDown : function(field, e){
98028         var k = e.getKey(),
98029             pageData = this.getPageData(),
98030             increment = e.shiftKey ? 10 : 1,
98031             pageNum,
98032             me = this;
98033
98034         if (k == e.RETURN) {
98035             e.stopEvent();
98036             pageNum = me.readPageFromInput(pageData);
98037             if (pageNum !== false) {
98038                 pageNum = Math.min(Math.max(1, pageNum), pageData.total);
98039                 if(me.fireEvent('beforechange', me, pageNum) !== false){
98040                     me.store.loadPage(pageNum);
98041                 }
98042             }
98043         } else if (k == e.HOME || k == e.END) {
98044             e.stopEvent();
98045             pageNum = k == e.HOME ? 1 : pageData.pageCount;
98046             field.setValue(pageNum);
98047         } else if (k == e.UP || k == e.PAGEUP || k == e.DOWN || k == e.PAGEDOWN) {
98048             e.stopEvent();
98049             pageNum = me.readPageFromInput(pageData);
98050             if (pageNum) {
98051                 if (k == e.DOWN || k == e.PAGEDOWN) {
98052                     increment *= -1;
98053                 }
98054                 pageNum += increment;
98055                 if (pageNum >= 1 && pageNum <= pageData.pages) {
98056                     field.setValue(pageNum);
98057                 }
98058             }
98059         }
98060     },
98061
98062     // private
98063     beforeLoad : function(){
98064         if(this.rendered && this.refresh){
98065             this.refresh.disable();
98066         }
98067     },
98068
98069     // private
98070     doLoad : function(start){
98071         if(this.fireEvent('beforechange', this, o) !== false){
98072             this.store.load();
98073         }
98074     },
98075
98076     /**
98077      * Move to the first page, has the same effect as clicking the 'first' button.
98078      */
98079     moveFirst : function(){
98080         var me = this;
98081         if(me.fireEvent('beforechange', me, 1) !== false){
98082             me.store.loadPage(1);
98083         }
98084     },
98085
98086     /**
98087      * Move to the previous page, has the same effect as clicking the 'previous' button.
98088      */
98089     movePrevious : function(){
98090         var me = this,
98091             prev = me.store.currentPage - 1;
98092         
98093         if(me.fireEvent('beforechange', me, prev) !== false){
98094             me.store.previousPage();
98095         }
98096     },
98097
98098     /**
98099      * Move to the next page, has the same effect as clicking the 'next' button.
98100      */
98101     moveNext : function(){
98102         var me = this;        
98103         if(me.fireEvent('beforechange', me, me.store.currentPage + 1) !== false){
98104             me.store.nextPage();
98105         }
98106     },
98107
98108     /**
98109      * Move to the last page, has the same effect as clicking the 'last' button.
98110      */
98111     moveLast : function(){
98112         var me = this, 
98113             last = this.getPageData().pageCount;
98114         
98115         if(me.fireEvent('beforechange', me, last) !== false){
98116             me.store.loadPage(last);
98117         }
98118     },
98119
98120     /**
98121      * Refresh the current page, has the same effect as clicking the 'refresh' button.
98122      */
98123     doRefresh : function(){
98124         var me = this,
98125             current = me.store.currentPage;
98126         
98127         if(me.fireEvent('beforechange', me, current) !== false){
98128             me.store.loadPage(current);
98129         }
98130     },
98131
98132     /**
98133      * Binds the paging toolbar to the specified {@link Ext.data.Store}
98134      * @param {Store} store The store to bind to this toolbar
98135      * @param {Boolean} initial (Optional) true to not remove listeners
98136      */
98137     bindStore : function(store, initial){
98138         var me = this;
98139         
98140         if (!initial && me.store) {
98141             if(store !== me.store && me.store.autoDestroy){
98142                 me.store.destroy();
98143             }else{
98144                 me.store.un('beforeload', me.beforeLoad, me);
98145                 me.store.un('load', me.onLoad, me);
98146                 me.store.un('exception', me.onLoadError, me);
98147             }
98148             if(!store){
98149                 me.store = null;
98150             }
98151         }
98152         if (store) {
98153             store = Ext.data.StoreManager.lookup(store);
98154             store.on({
98155                 scope: me,
98156                 beforeload: me.beforeLoad,
98157                 load: me.onLoad,
98158                 exception: me.onLoadError
98159             });
98160         }
98161         me.store = store;
98162     },
98163
98164     /**
98165      * Unbinds the paging toolbar from the specified {@link Ext.data.Store} <b>(deprecated)</b>
98166      * @param {Ext.data.Store} store The data store to unbind
98167      */
98168     unbind : function(store){
98169         this.bindStore(null);
98170     },
98171
98172     /**
98173      * Binds the paging toolbar to the specified {@link Ext.data.Store} <b>(deprecated)</b>
98174      * @param {Ext.data.Store} store The data store to bind
98175      */
98176     bind : function(store){
98177         this.bindStore(store);
98178     },
98179
98180     // private
98181     onDestroy : function(){
98182         this.bindStore(null);
98183         this.callParent();
98184     }
98185 });
98186
98187 /**
98188  * @class Ext.view.BoundList
98189  * @extends Ext.view.View
98190  * An internal used DataView for ComboBox, MultiSelect and ItemSelector.
98191  */
98192 Ext.define('Ext.view.BoundList', {
98193     extend: 'Ext.view.View',
98194     alias: 'widget.boundlist',
98195     alternateClassName: 'Ext.BoundList',
98196     requires: ['Ext.layout.component.BoundList', 'Ext.toolbar.Paging'],
98197
98198     /**
98199      * @cfg {Number} pageSize If greater than <tt>0</tt>, a {@link Ext.toolbar.Paging} is displayed at the
98200      * bottom of the list and store queries will execute with page start and
98201      * {@link Ext.toolbar.Paging#pageSize limit} parameters.
98202      */
98203     pageSize: 0,
98204
98205     /**
98206      * @property pagingToolbar
98207      * @type {Ext.toolbar.Paging}
98208      * A reference to the PagingToolbar instance in this view. Only populated if {@link #pageSize} is greater
98209      * than zero and the BoundList has been rendered.
98210      */
98211
98212     // private overrides
98213     autoScroll: true,
98214     baseCls: Ext.baseCSSPrefix + 'boundlist',
98215     listItemCls: '',
98216     shadow: false,
98217     trackOver: true,
98218     refreshed: 0,
98219
98220     ariaRole: 'listbox',
98221
98222     componentLayout: 'boundlist',
98223
98224     renderTpl: ['<div class="list-ct"></div>'],
98225
98226     initComponent: function() {
98227         var me = this,
98228             baseCls = me.baseCls,
98229             itemCls = baseCls + '-item';
98230         me.itemCls = itemCls;
98231         me.selectedItemCls = baseCls + '-selected';
98232         me.overItemCls = baseCls + '-item-over';
98233         me.itemSelector = "." + itemCls;
98234
98235         if (me.floating) {
98236             me.addCls(baseCls + '-floating');
98237         }
98238
98239         // should be setting aria-posinset based on entire set of data
98240         // not filtered set
98241         me.tpl = Ext.create('Ext.XTemplate', 
98242             '<ul><tpl for=".">',
98243                 '<li role="option" class="' + itemCls + '">' + me.getInnerTpl(me.displayField) + '</li>',
98244             '</tpl></ul>'
98245         );
98246
98247         if (me.pageSize) {
98248             me.pagingToolbar = me.createPagingToolbar();
98249         }
98250
98251         me.callParent();
98252
98253         Ext.applyIf(me.renderSelectors, {
98254             listEl: '.list-ct'
98255         });
98256     },
98257
98258     createPagingToolbar: function() {
98259         return Ext.widget('pagingtoolbar', {
98260             pageSize: this.pageSize,
98261             store: this.store,
98262             border: false
98263         });
98264     },
98265
98266     onRender: function() {
98267         var me = this,
98268             toolbar = me.pagingToolbar;
98269         me.callParent(arguments);
98270         if (toolbar) {
98271             toolbar.render(me.el);
98272         }
98273     },
98274
98275     bindStore : function(store, initial) {
98276         var me = this,
98277             toolbar = me.pagingToolbar;
98278         me.callParent(arguments);
98279         if (toolbar) {
98280             toolbar.bindStore(store, initial);
98281         }
98282     },
98283
98284     getTargetEl: function() {
98285         return this.listEl || this.el;
98286     },
98287
98288     getInnerTpl: function(displayField) {
98289         return '{' + displayField + '}';
98290     },
98291
98292     refresh: function() {
98293         var me = this;
98294         me.callParent();
98295         if (me.isVisible()) {
98296             me.refreshed++;
98297             me.doComponentLayout();
98298             me.refreshed--;
98299         }
98300     },
98301     
98302     initAria: function() {
98303         this.callParent();
98304         
98305         var selModel = this.getSelectionModel(),
98306             mode     = selModel.getSelectionMode(),
98307             actionEl = this.getActionEl();
98308         
98309         // TODO: subscribe to mode changes or allow the selModel to manipulate this attribute.
98310         if (mode !== 'SINGLE') {
98311             actionEl.dom.setAttribute('aria-multiselectable', true);
98312         }
98313     },
98314
98315     onDestroy: function() {
98316         Ext.destroyMembers(this, 'pagingToolbar', 'listEl');
98317         this.callParent();
98318     }
98319 });
98320
98321 /**
98322  * @class Ext.view.BoundListKeyNav
98323  * @extends Ext.util.KeyNav
98324  * A specialized {@link Ext.util.KeyNav} implementation for navigating a {@link Ext.view.BoundList} using
98325  * the keyboard. The up, down, pageup, pagedown, home, and end keys move the active highlight
98326  * through the list. The enter key invokes the selection model's select action using the highlighted item.
98327  */
98328 Ext.define('Ext.view.BoundListKeyNav', {
98329     extend: 'Ext.util.KeyNav',
98330     requires: 'Ext.view.BoundList',
98331
98332     /**
98333      * @cfg {Ext.view.BoundList} boundList
98334      * @required
98335      * The {@link Ext.view.BoundList} instance for which key navigation will be managed. This is required.
98336      */
98337
98338     constructor: function(el, config) {
98339         var me = this;
98340         me.boundList = config.boundList;
98341         me.callParent([el, Ext.apply({}, config, me.defaultHandlers)]);
98342     },
98343
98344     defaultHandlers: {
98345         up: function() {
98346             var me = this,
98347                 boundList = me.boundList,
98348                 allItems = boundList.all,
98349                 oldItem = boundList.highlightedItem,
98350                 oldItemIdx = oldItem ? boundList.indexOf(oldItem) : -1,
98351                 newItemIdx = oldItemIdx > 0 ? oldItemIdx - 1 : allItems.getCount() - 1; //wraps around
98352             me.highlightAt(newItemIdx);
98353         },
98354
98355         down: function() {
98356             var me = this,
98357                 boundList = me.boundList,
98358                 allItems = boundList.all,
98359                 oldItem = boundList.highlightedItem,
98360                 oldItemIdx = oldItem ? boundList.indexOf(oldItem) : -1,
98361                 newItemIdx = oldItemIdx < allItems.getCount() - 1 ? oldItemIdx + 1 : 0; //wraps around
98362             me.highlightAt(newItemIdx);
98363         },
98364
98365         pageup: function() {
98366             //TODO
98367         },
98368
98369         pagedown: function() {
98370             //TODO
98371         },
98372
98373         home: function() {
98374             this.highlightAt(0);
98375         },
98376
98377         end: function() {
98378             var me = this;
98379             me.highlightAt(me.boundList.all.getCount() - 1);
98380         },
98381
98382         enter: function(e) {
98383             this.selectHighlighted(e);
98384         }
98385     },
98386
98387     /**
98388      * Highlights the item at the given index.
98389      * @param {Number} index
98390      */
98391     highlightAt: function(index) {
98392         var boundList = this.boundList,
98393             item = boundList.all.item(index);
98394         if (item) {
98395             item = item.dom;
98396             boundList.highlightItem(item);
98397             boundList.getTargetEl().scrollChildIntoView(item, false);
98398         }
98399     },
98400
98401     /**
98402      * Triggers selection of the currently highlighted item according to the behavior of
98403      * the configured SelectionModel.
98404      */
98405     selectHighlighted: function(e) {
98406         var me = this,
98407             boundList = me.boundList,
98408             highlighted = boundList.highlightedItem,
98409             selModel = boundList.getSelectionModel();
98410         if (highlighted) {
98411             selModel.selectWithEvent(boundList.getRecord(highlighted), e);
98412         }
98413     }
98414
98415 });
98416 /**
98417  * @class Ext.form.field.ComboBox
98418  * @extends Ext.form.field.Picker
98419  *
98420  * A combobox control with support for autocomplete, remote loading, and many other features.
98421  *
98422  * A ComboBox is like a combination of a traditional HTML text `&lt;input&gt;` field and a `&lt;select&gt;`
98423  * field; the user is able to type freely into the field, and/or pick values from a dropdown selection
98424  * list. The user can input any value by default, even if it does not appear in the selection list;
98425  * to prevent free-form values and restrict them to items in the list, set {@link #forceSelection} to `true`.
98426  *
98427  * The selection list's options are populated from any {@link Ext.data.Store}, including remote
98428  * stores. The data items in the store are mapped to each option's displayed text and backing value via
98429  * the {@link #valueField} and {@link #displayField} configurations, respectively.
98430  *
98431  * If your store is not remote, i.e. it depends only on local data and is loaded up front, you should be
98432  * sure to set the {@link #queryMode} to `'local'`, as this will improve responsiveness for the user.
98433  *
98434  * {@img Ext.form.ComboBox/Ext.form.ComboBox.png Ext.form.ComboBox component}
98435  *
98436  * ## Example usage:
98437  *
98438  *     // The data store containing the list of states
98439  *     var states = Ext.create('Ext.data.Store', {
98440  *         fields: ['abbr', 'name'],
98441  *         data : [
98442  *             {"abbr":"AL", "name":"Alabama"},
98443  *             {"abbr":"AK", "name":"Alaska"},
98444  *             {"abbr":"AZ", "name":"Arizona"}
98445  *             //...
98446  *         ]
98447  *     });
98448  *
98449  *     // Create the combo box, attached to the states data store
98450  *     Ext.create('Ext.form.ComboBox', {
98451  *         fieldLabel: 'Choose State',
98452  *         store: states,
98453  *         queryMode: 'local',
98454  *         displayField: 'name',
98455  *         valueField: 'abbr',
98456  *         renderTo: Ext.getBody()
98457  *     });
98458  *
98459  * ## Events
98460  *
98461  * To do something when something in ComboBox is selected, configure the select event:
98462  *
98463  *     var cb = Ext.create('Ext.form.ComboBox', {
98464  *         // all of your config options
98465  *         listeners:{
98466  *              scope: yourScope,
98467  *              'select': yourFunction
98468  *         }
98469  *     });
98470  *
98471  *     // Alternatively, you can assign events after the object is created:
98472  *     var cb = new Ext.form.field.ComboBox(yourOptions);
98473  *     cb.on('select', yourFunction, yourScope);
98474  *
98475  * ## Multiple Selection
98476  *
98477  * ComboBox also allows selection of multiple items from the list; to enable multi-selection set the
98478  * {@link #multiSelect} config to `true`.
98479  *
98480  * @constructor
98481  * Create a new ComboBox.
98482  * @param {Object} config Configuration options
98483  * @xtype combo
98484  * @docauthor Jason Johnston <jason@sencha.com>
98485  */
98486 Ext.define('Ext.form.field.ComboBox', {
98487     extend:'Ext.form.field.Picker',
98488     requires: ['Ext.util.DelayedTask', 'Ext.EventObject', 'Ext.view.BoundList', 'Ext.view.BoundListKeyNav', 'Ext.data.StoreManager'],
98489     alternateClassName: 'Ext.form.ComboBox',
98490     alias: ['widget.combobox', 'widget.combo'],
98491
98492     /**
98493      * @cfg {String} triggerCls
98494      * An additional CSS class used to style the trigger button. The trigger will always get the
98495      * {@link #triggerBaseCls} by default and <tt>triggerCls</tt> will be <b>appended</b> if specified.
98496      * Defaults to 'x-form-arrow-trigger' for ComboBox.
98497      */
98498     triggerCls: Ext.baseCSSPrefix + 'form-arrow-trigger',
98499
98500     /**
98501      * @cfg {Ext.data.Store/Array} store The data source to which this combo is bound (defaults to <tt>undefined</tt>).
98502      * Acceptable values for this property are:
98503      * <div class="mdetail-params"><ul>
98504      * <li><b>any {@link Ext.data.Store Store} subclass</b></li>
98505      * <li><b>an Array</b> : Arrays will be converted to a {@link Ext.data.Store} internally,
98506      * automatically generating {@link Ext.data.Field#name field names} to work with all data components.
98507      * <div class="mdetail-params"><ul>
98508      * <li><b>1-dimensional array</b> : (e.g., <tt>['Foo','Bar']</tt>)<div class="sub-desc">
98509      * A 1-dimensional array will automatically be expanded (each array item will be used for both the combo
98510      * {@link #valueField} and {@link #displayField})</div></li>
98511      * <li><b>2-dimensional array</b> : (e.g., <tt>[['f','Foo'],['b','Bar']]</tt>)<div class="sub-desc">
98512      * For a multi-dimensional array, the value in index 0 of each item will be assumed to be the combo
98513      * {@link #valueField}, while the value at index 1 is assumed to be the combo {@link #displayField}.
98514      * </div></li></ul></div></li></ul></div>
98515      * <p>See also <tt>{@link #queryMode}</tt>.</p>
98516      */
98517
98518     /**
98519      * @cfg {Boolean} multiSelect
98520      * If set to <tt>true</tt>, allows the combo field to hold more than one value at a time, and allows selecting
98521      * multiple items from the dropdown list. The combo's text field will show all selected values separated by
98522      * the {@link #delimiter}. (Defaults to <tt>false</tt>.)
98523      */
98524     multiSelect: false,
98525
98526     /**
98527      * @cfg {String} delimiter
98528      * The character(s) used to separate the {@link #displayField display values} of multiple selected items
98529      * when <tt>{@link #multiSelect} = true</tt>. Defaults to <tt>', '</tt>.
98530      */
98531     delimiter: ', ',
98532
98533     /**
98534      * @cfg {String} displayField The underlying {@link Ext.data.Field#name data field name} to bind to this
98535      * ComboBox (defaults to 'text').
98536      * <p>See also <tt>{@link #valueField}</tt>.</p>
98537      */
98538     displayField: 'text',
98539
98540     /**
98541      * @cfg {String} valueField
98542      * @required
98543      * The underlying {@link Ext.data.Field#name data value name} to bind to this ComboBox (defaults to match
98544      * the value of the {@link #displayField} config).
98545      * <p><b>Note</b>: use of a <tt>valueField</tt> requires the user to make a selection in order for a value to be
98546      * mapped. See also <tt>{@link #displayField}</tt>.</p>
98547      */
98548
98549     /**
98550      * @cfg {String} triggerAction The action to execute when the trigger is clicked.
98551      * <div class="mdetail-params"><ul>
98552      * <li><b><tt>'all'</tt></b> : <b>Default</b>
98553      * <p class="sub-desc">{@link #doQuery run the query} specified by the <tt>{@link #allQuery}</tt> config option</p></li>
98554      * <li><b><tt>'query'</tt></b> :
98555      * <p class="sub-desc">{@link #doQuery run the query} using the {@link Ext.form.field.Base#getRawValue raw value}.</p></li>
98556      * </ul></div>
98557      * <p>See also <code>{@link #queryParam}</code>.</p>
98558      */
98559     triggerAction: 'all',
98560
98561     /**
98562      * @cfg {String} allQuery The text query to send to the server to return all records for the list
98563      * with no filtering (defaults to '')
98564      */
98565     allQuery: '',
98566
98567     /**
98568      * @cfg {String} queryParam Name of the query ({@link Ext.data.Store#baseParam baseParam} name for the store)
98569      * as it will be passed on the querystring (defaults to <tt>'query'</tt>)
98570      */
98571     queryParam: 'query',
98572
98573     /**
98574      * @cfg {String} queryMode
98575      * The mode for queries. Acceptable values are:
98576      * <div class="mdetail-params"><ul>
98577      * <li><b><tt>'remote'</tt></b> : <b>Default</b>
98578      * <p class="sub-desc">Automatically loads the <tt>{@link #store}</tt> the <b>first</b> time the trigger
98579      * is clicked. If you do not want the store to be automatically loaded the first time the trigger is
98580      * clicked, set to <tt>'local'</tt> and manually load the store.  To force a requery of the store
98581      * <b>every</b> time the trigger is clicked see <tt>{@link #lastQuery}</tt>.</p></li>
98582      * <li><b><tt>'local'</tt></b> :
98583      * <p class="sub-desc">ComboBox loads local data</p>
98584      * <pre><code>
98585 var combo = new Ext.form.field.ComboBox({
98586     renderTo: document.body,
98587     queryMode: 'local',
98588     store: new Ext.data.ArrayStore({
98589         id: 0,
98590         fields: [
98591             'myId',  // numeric value is the key
98592             'displayText'
98593         ],
98594         data: [[1, 'item1'], [2, 'item2']]  // data is local
98595     }),
98596     valueField: 'myId',
98597     displayField: 'displayText',
98598     triggerAction: 'all'
98599 });
98600      * </code></pre></li>
98601      * </ul></div>
98602      */
98603     queryMode: 'remote',
98604
98605     queryCaching: true,
98606
98607     /**
98608      * @cfg {Number} pageSize If greater than <tt>0</tt>, a {@link Ext.toolbar.Paging} is displayed in the
98609      * footer of the dropdown list and the {@link #doQuery filter queries} will execute with page start and
98610      * {@link Ext.toolbar.Paging#pageSize limit} parameters. Only applies when <tt>{@link #queryMode} = 'remote'</tt>
98611      * (defaults to <tt>0</tt>).
98612      */
98613     pageSize: 0,
98614
98615     /**
98616      * @cfg {Number} queryDelay The length of time in milliseconds to delay between the start of typing and
98617      * sending the query to filter the dropdown list (defaults to <tt>500</tt> if <tt>{@link #queryMode} = 'remote'</tt>
98618      * or <tt>10</tt> if <tt>{@link #queryMode} = 'local'</tt>)
98619      */
98620
98621     /**
98622      * @cfg {Number} minChars The minimum number of characters the user must type before autocomplete and
98623      * {@link #typeAhead} activate (defaults to <tt>4</tt> if <tt>{@link #queryMode} = 'remote'</tt> or <tt>0</tt> if
98624      * <tt>{@link #queryMode} = 'local'</tt>, does not apply if <tt>{@link Ext.form.field.Trigger#editable editable} = false</tt>).
98625      */
98626
98627     /**
98628      * @cfg {Boolean} autoSelect <tt>true</tt> to select the first result gathered by the data store (defaults
98629      * to <tt>true</tt>).  A false value would require a manual selection from the dropdown list to set the components value
98630      * unless the value of ({@link #typeAhead}) were true.
98631      */
98632     autoSelect: true,
98633
98634     /**
98635      * @cfg {Boolean} typeAhead <tt>true</tt> to populate and autoselect the remainder of the text being
98636      * typed after a configurable delay ({@link #typeAheadDelay}) if it matches a known value (defaults
98637      * to <tt>false</tt>)
98638      */
98639     typeAhead: false,
98640
98641     /**
98642      * @cfg {Number} typeAheadDelay The length of time in milliseconds to wait until the typeahead text is displayed
98643      * if <tt>{@link #typeAhead} = true</tt> (defaults to <tt>250</tt>)
98644      */
98645     typeAheadDelay: 250,
98646
98647     /**
98648      * @cfg {Boolean} selectOnTab
98649      * Whether the Tab key should select the currently highlighted item. Defaults to <tt>true</tt>.
98650      */
98651     selectOnTab: true,
98652
98653     /**
98654      * @cfg {Boolean} forceSelection <tt>true</tt> to restrict the selected value to one of the values in the list,
98655      * <tt>false</tt> to allow the user to set arbitrary text into the field (defaults to <tt>false</tt>)
98656      */
98657     forceSelection: false,
98658
98659     /**
98660      * @cfg {String} valueNotFoundText When using a name/value combo, if the value passed to setValue is not found in
98661      * the store, valueNotFoundText will be displayed as the field text if defined (defaults to undefined). If this
98662      * default text is used, it means there is no value set and no validation will occur on this field.
98663      */
98664
98665     /**
98666      * The value of the match string used to filter the store. Delete this property to force a requery.
98667      * Example use:
98668      * <pre><code>
98669 var combo = new Ext.form.field.ComboBox({
98670     ...
98671     queryMode: 'remote',
98672     listeners: {
98673         // delete the previous query in the beforequery event or set
98674         // combo.lastQuery = null (this will reload the store the next time it expands)
98675         beforequery: function(qe){
98676             delete qe.combo.lastQuery;
98677         }
98678     }
98679 });
98680      * </code></pre>
98681      * To make sure the filter in the store is not cleared the first time the ComboBox trigger is used
98682      * configure the combo with <tt>lastQuery=''</tt>. Example use:
98683      * <pre><code>
98684 var combo = new Ext.form.field.ComboBox({
98685     ...
98686     queryMode: 'local',
98687     triggerAction: 'all',
98688     lastQuery: ''
98689 });
98690      * </code></pre>
98691      * @property lastQuery
98692      * @type String
98693      */
98694
98695     /**
98696      * @cfg {Object} defaultListConfig
98697      * Set of options that will be used as defaults for the user-configured {@link #listConfig} object.
98698      */
98699     defaultListConfig: {
98700         emptyText: '',
98701         loadingText: 'Loading...',
98702         loadingHeight: 70,
98703         minWidth: 70,
98704         maxHeight: 300,
98705         shadow: 'sides'
98706     },
98707
98708     /**
98709      * @cfg {Mixed} transform
98710      * The id, DOM node or {@link Ext.core.Element} of an existing HTML <tt>&lt;select&gt;</tt> element to
98711      * convert into a ComboBox. The target select's options will be used to build the options in the ComboBox
98712      * dropdown; a configured {@link #store} will take precedence over this.
98713      */
98714
98715     /**
98716      * @cfg {Object} listConfig
98717      * <p>An optional set of configuration properties that will be passed to the {@link Ext.view.BoundList}'s
98718      * constructor. Any configuration that is valid for BoundList can be included. Some of the more useful
98719      * ones are:</p>
98720      * <ul>
98721      *     <li>{@link Ext.view.BoundList#cls} - defaults to empty</li>
98722      *     <li>{@link Ext.view.BoundList#emptyText} - defaults to empty string</li>
98723      *     <li>{@link Ext.view.BoundList#getInnerTpl} - defaults to the template defined in BoundList</li>
98724      *     <li>{@link Ext.view.BoundList#itemSelector} - defaults to the value defined in BoundList</li>
98725      *     <li>{@link Ext.view.BoundList#loadingText} - defaults to <tt>'Loading...'</tt></li>
98726      *     <li>{@link Ext.view.BoundList#minWidth} - defaults to <tt>70</tt></li>
98727      *     <li>{@link Ext.view.BoundList#maxWidth} - defaults to <tt>undefined</tt></li>
98728      *     <li>{@link Ext.view.BoundList#maxHeight} - defaults to <tt>300</tt></li>
98729      *     <li>{@link Ext.view.BoundList#resizable} - defaults to <tt>false</tt></li>
98730      *     <li>{@link Ext.view.BoundList#shadow} - defaults to <tt>'sides'</tt></li>
98731      *     <li>{@link Ext.view.BoundList#width} - defaults to <tt>undefined</tt> (automatically set to the width
98732      *         of the ComboBox field if {@link #matchFieldWidth} is true)</li>
98733      * </ul>
98734      */
98735
98736     //private
98737     ignoreSelection: 0,
98738
98739     initComponent: function() {
98740         var me = this,
98741             isDefined = Ext.isDefined,
98742             store = me.store,
98743             transform = me.transform,
98744             transformSelect, isLocalMode;
98745
98746         if (!store && !transform) {
98747             Ext.Error.raise('Either a valid store, or a HTML select to transform, must be configured on the combo.');
98748         }
98749         if (me.typeAhead && me.multiSelect) {
98750             Ext.Error.raise('typeAhead and multiSelect are mutually exclusive options -- please remove one of them.');
98751         }
98752         if (me.typeAhead && !me.editable) {
98753             Ext.Error.raise('If typeAhead is enabled the combo must be editable: true -- please change one of those settings.');
98754         }
98755         if (me.selectOnFocus && !me.editable) {
98756             Ext.Error.raise('If selectOnFocus is enabled the combo must be editable: true -- please change one of those settings.');
98757         }
98758
98759         this.addEvents(
98760             // TODO need beforeselect?
98761
98762             /**
98763              * @event beforequery
98764              * Fires before all queries are processed. Return false to cancel the query or set the queryEvent's
98765              * cancel property to true.
98766              * @param {Object} queryEvent An object that has these properties:<ul>
98767              * <li><code>combo</code> : Ext.form.field.ComboBox <div class="sub-desc">This combo box</div></li>
98768              * <li><code>query</code> : String <div class="sub-desc">The query string</div></li>
98769              * <li><code>forceAll</code> : Boolean <div class="sub-desc">True to force "all" query</div></li>
98770              * <li><code>cancel</code> : Boolean <div class="sub-desc">Set to true to cancel the query</div></li>
98771              * </ul>
98772              */
98773             'beforequery',
98774
98775             /*
98776              * @event select
98777              * Fires when at least one list item is selected.
98778              * @param {Ext.form.field.ComboBox} combo This combo box
98779              * @param {Array} records The selected records
98780              */
98781             'select'
98782         );
98783
98784         // Build store from 'transform' HTML select element's options
98785         if (!store && transform) {
98786             transformSelect = Ext.getDom(transform);
98787             if (transformSelect) {
98788                 store = Ext.Array.map(Ext.Array.from(transformSelect.options), function(option) {
98789                     return [option.value, option.text];
98790                 });
98791                 if (!me.name) {
98792                     me.name = transformSelect.name;
98793                 }
98794                 if (!('value' in me)) {
98795                     me.value = transformSelect.value;
98796                 }
98797             }
98798         }
98799
98800         me.bindStore(store, true);
98801         store = me.store;
98802         if (store.autoCreated) {
98803             me.queryMode = 'local';
98804             me.valueField = me.displayField = 'field1';
98805             if (!store.expanded) {
98806                 me.displayField = 'field2';
98807             }
98808         }
98809
98810
98811         if (!isDefined(me.valueField)) {
98812             me.valueField = me.displayField;
98813         }
98814
98815         isLocalMode = me.queryMode === 'local';
98816         if (!isDefined(me.queryDelay)) {
98817             me.queryDelay = isLocalMode ? 10 : 500;
98818         }
98819         if (!isDefined(me.minChars)) {
98820             me.minChars = isLocalMode ? 0 : 4;
98821         }
98822
98823         if (!me.displayTpl) {
98824             me.displayTpl = Ext.create('Ext.XTemplate',
98825                 '<tpl for=".">' +
98826                     '{[typeof values === "string" ? values : values.' + me.displayField + ']}' +
98827                     '<tpl if="xindex < xcount">' + me.delimiter + '</tpl>' +
98828                 '</tpl>'
98829             );
98830         } else if (Ext.isString(me.displayTpl)) {
98831             me.displayTpl = Ext.create('Ext.XTemplate', me.displayTpl);
98832         }
98833
98834         me.callParent();
98835
98836         me.doQueryTask = Ext.create('Ext.util.DelayedTask', me.doRawQuery, me);
98837
98838         // store has already been loaded, setValue
98839         if (me.store.getCount() > 0) {
98840             me.setValue(me.value);
98841         }
98842
98843         // render in place of 'transform' select
98844         if (transformSelect) {
98845             me.render(transformSelect.parentNode, transformSelect);
98846             Ext.removeNode(transformSelect);
98847             delete me.renderTo;
98848         }
98849     },
98850
98851     beforeBlur: function() {
98852         var me = this;
98853         me.doQueryTask.cancel();
98854         if (me.forceSelection) {
98855             me.assertValue();
98856         } else {
98857             me.collapse();
98858         }
98859     },
98860
98861     // private
98862     assertValue: function() {
98863         var me = this,
98864             value = me.getRawValue(),
98865             rec;
98866
98867         if (me.multiSelect) {
98868             // For multiselect, check that the current displayed value matches the current
98869             // selection, if it does not then revert to the most recent selection.
98870             if (value !== me.getDisplayValue()) {
98871                 me.setValue(me.lastSelection);
98872             }
98873         } else {
98874             // For single-select, match the displayed value to a record and select it,
98875             // if it does not match a record then revert to the most recent selection.
98876             rec = me.findRecordByDisplay(value);
98877             if (rec) {
98878                 me.select(rec);
98879             } else {
98880                 me.setValue(me.lastSelection);
98881             }
98882         }
98883         me.collapse();
98884     },
98885
98886     onTypeAhead: function() {
98887         var me = this,
98888             displayField = me.displayField,
98889             record = me.store.findRecord(displayField, me.getRawValue()),
98890             boundList = me.getPicker(),
98891             newValue, len, selStart;
98892
98893         if (record) {
98894             newValue = record.get(displayField);
98895             len = newValue.length;
98896             selStart = me.getRawValue().length;
98897
98898             boundList.highlightItem(boundList.getNode(record));
98899
98900             if (selStart !== 0 && selStart !== len) {
98901                 me.setRawValue(newValue);
98902                 me.selectText(selStart, newValue.length);
98903             }
98904         }
98905     },
98906
98907     // invoked when a different store is bound to this combo
98908     // than the original
98909     resetToDefault: function() {
98910
98911     },
98912
98913     bindStore: function(store, initial) {
98914         var me = this,
98915             oldStore = me.store;
98916
98917         // this code directly accesses this.picker, bc invoking getPicker
98918         // would create it when we may be preping to destroy it
98919         if (oldStore && !initial) {
98920             if (oldStore !== store && oldStore.autoDestroy) {
98921                 oldStore.destroy();
98922             } else {
98923                 oldStore.un({
98924                     scope: me,
98925                     load: me.onLoad,
98926                     exception: me.collapse
98927                 });
98928             }
98929             if (!store) {
98930                 me.store = null;
98931                 if (me.picker) {
98932                     me.picker.bindStore(null);
98933                 }
98934             }
98935         }
98936         if (store) {
98937             if (!initial) {
98938                 me.resetToDefault();
98939             }
98940
98941             me.store = Ext.data.StoreManager.lookup(store);
98942             me.store.on({
98943                 scope: me,
98944                 load: me.onLoad,
98945                 exception: me.collapse
98946             });
98947
98948             if (me.picker) {
98949                 me.picker.bindStore(store);
98950             }
98951         }
98952     },
98953
98954     onLoad: function() {
98955         var me = this,
98956             value = me.value;
98957
98958         me.syncSelection();
98959         if (me.picker && !me.picker.getSelectionModel().hasSelection()) {
98960             me.doAutoSelect();
98961         }
98962     },
98963
98964     /**
98965      * @private
98966      * Execute the query with the raw contents within the textfield.
98967      */
98968     doRawQuery: function() {
98969         this.doQuery(this.getRawValue());
98970     },
98971
98972     /**
98973      * Executes a query to filter the dropdown list. Fires the {@link #beforequery} event prior to performing the
98974      * query allowing the query action to be canceled if needed.
98975      * @param {String} queryString The SQL query to execute
98976      * @param {Boolean} forceAll <tt>true</tt> to force the query to execute even if there are currently fewer
98977      * characters in the field than the minimum specified by the <tt>{@link #minChars}</tt> config option.  It
98978      * also clears any filter previously saved in the current store (defaults to <tt>false</tt>)
98979      * @return {Boolean} true if the query was permitted to run, false if it was cancelled by a {@link #beforequery} handler.
98980      */
98981     doQuery: function(queryString, forceAll) {
98982         queryString = queryString || '';
98983
98984         // store in object and pass by reference in 'beforequery'
98985         // so that client code can modify values.
98986         var me = this,
98987             qe = {
98988                 query: queryString,
98989                 forceAll: forceAll,
98990                 combo: me,
98991                 cancel: false
98992             },
98993             store = me.store,
98994             isLocalMode = me.queryMode === 'local';
98995
98996         if (me.fireEvent('beforequery', qe) === false || qe.cancel) {
98997             return false;
98998         }
98999
99000         // get back out possibly modified values
99001         queryString = qe.query;
99002         forceAll = qe.forceAll;
99003
99004         // query permitted to run
99005         if (forceAll || (queryString.length >= me.minChars)) {
99006             // expand before starting query so LoadMask can position itself correctly
99007             me.expand();
99008
99009             // make sure they aren't querying the same thing
99010             if (!me.queryCaching || me.lastQuery !== queryString) {
99011                 me.lastQuery = queryString;
99012                 store.clearFilter(!forceAll);
99013                 if (isLocalMode) {
99014                     if (!forceAll) {
99015                         store.filter(me.displayField, queryString);
99016                     }
99017                 } else {
99018                     store.load({
99019                         params: me.getParams(queryString)
99020                     });
99021                 }
99022             }
99023
99024             // Clear current selection if it does not match the current value in the field
99025             if (me.getRawValue() !== me.getDisplayValue()) {
99026                 me.ignoreSelection++;
99027                 me.picker.getSelectionModel().deselectAll();
99028                 me.ignoreSelection--;
99029             }
99030
99031             if (isLocalMode) {
99032                 me.doAutoSelect();
99033             }
99034             if (me.typeAhead) {
99035                 me.doTypeAhead();
99036             }
99037         }
99038         return true;
99039     },
99040
99041     // private
99042     getParams: function(queryString) {
99043         var p = {},
99044             pageSize = this.pageSize;
99045         p[this.queryParam] = queryString;
99046         if (pageSize) {
99047             p.start = 0;
99048             p.limit = pageSize;
99049         }
99050         return p;
99051     },
99052
99053     /**
99054      * @private
99055      * If the autoSelect config is true, and the picker is open, highlights the first item.
99056      */
99057     doAutoSelect: function() {
99058         var me = this,
99059             picker = me.picker,
99060             lastSelected, itemNode;
99061         if (picker && me.autoSelect && me.store.getCount() > 0) {
99062             // Highlight the last selected item and scroll it into view
99063             lastSelected = picker.getSelectionModel().lastSelected;
99064             itemNode = picker.getNode(lastSelected || 0);
99065             if (itemNode) {
99066                 picker.highlightItem(itemNode);
99067                 picker.listEl.scrollChildIntoView(itemNode, false);
99068             }
99069         }
99070     },
99071
99072     doTypeAhead: function() {
99073         if (!this.typeAheadTask) {
99074             this.typeAheadTask = Ext.create('Ext.util.DelayedTask', this.onTypeAhead, this);
99075         }
99076         if (this.lastKey != Ext.EventObject.BACKSPACE && this.lastKey != Ext.EventObject.DELETE) {
99077             this.typeAheadTask.delay(this.typeAheadDelay);
99078         }
99079     },
99080
99081     onTriggerClick: function() {
99082         var me = this;
99083         if (!me.readOnly && !me.disabled) {
99084             if (me.isExpanded) {
99085                 me.collapse();
99086             } else {
99087                 me.onFocus({});
99088                 if (me.triggerAction === 'all') {
99089                     me.doQuery(me.allQuery, true);
99090                 } else {
99091                     me.doQuery(me.getRawValue());
99092                 }
99093             }
99094             me.inputEl.focus();
99095         }
99096     },
99097
99098
99099     // store the last key and doQuery if relevant
99100     onKeyUp: function(e, t) {
99101         var me = this,
99102             key = e.getKey();
99103
99104         if (!me.readOnly && !me.disabled && me.editable) {
99105             me.lastKey = key;
99106             // we put this in a task so that we can cancel it if a user is
99107             // in and out before the queryDelay elapses
99108
99109             // perform query w/ any normal key or backspace or delete
99110             if (!e.isSpecialKey() || key == e.BACKSPACE || key == e.DELETE) {
99111                 me.doQueryTask.delay(me.queryDelay);
99112             }
99113         }
99114     },
99115
99116     initEvents: function() {
99117         var me = this;
99118         me.callParent();
99119
99120         // setup keyboard handling
99121         me.mon(me.inputEl, 'keyup', me.onKeyUp, me);
99122     },
99123
99124     createPicker: function() {
99125         var me = this,
99126             picker,
99127             menuCls = Ext.baseCSSPrefix + 'menu',
99128             opts = Ext.apply({
99129                 selModel: {
99130                     mode: me.multiSelect ? 'SIMPLE' : 'SINGLE'
99131                 },
99132                 floating: true,
99133                 hidden: true,
99134                 ownerCt: me.ownerCt,
99135                 cls: me.el.up('.' + menuCls) ? menuCls : '',
99136                 store: me.store,
99137                 displayField: me.displayField,
99138                 focusOnToFront: false,
99139                 pageSize: me.pageSize
99140             }, me.listConfig, me.defaultListConfig);
99141
99142         picker = me.picker = Ext.create('Ext.view.BoundList', opts);
99143
99144         me.mon(picker, {
99145             itemclick: me.onItemClick,
99146             refresh: me.onListRefresh,
99147             scope: me
99148         });
99149
99150         me.mon(picker.getSelectionModel(), {
99151             selectionChange: me.onListSelectionChange,
99152             scope: me
99153         });
99154
99155         return picker;
99156     },
99157
99158     onListRefresh: function() {
99159         this.alignPicker();
99160         this.syncSelection();
99161     },
99162     
99163     onItemClick: function(picker, record){
99164         /*
99165          * If we're doing single selection, the selection change events won't fire when
99166          * clicking on the selected element. Detect it here.
99167          */
99168         var me = this,
99169             lastSelection = me.lastSelection,
99170             valueField = me.valueField,
99171             selected;
99172         
99173         if (!me.multiSelect && lastSelection) {
99174             selected = lastSelection[0];
99175             if (record.get(valueField) === selected.get(valueField)) {
99176                 me.collapse();
99177             }
99178         }   
99179     },
99180
99181     onListSelectionChange: function(list, selectedRecords) {
99182         var me = this;
99183         // Only react to selection if it is not called from setValue, and if our list is
99184         // expanded (ignores changes to the selection model triggered elsewhere)
99185         if (!me.ignoreSelection && me.isExpanded) {
99186             if (!me.multiSelect) {
99187                 Ext.defer(me.collapse, 1, me);
99188             }
99189             me.setValue(selectedRecords, false);
99190             if (selectedRecords.length > 0) {
99191                 me.fireEvent('select', me, selectedRecords);
99192             }
99193             me.inputEl.focus();
99194         }
99195     },
99196
99197     /**
99198      * @private
99199      * Enables the key nav for the BoundList when it is expanded.
99200      */
99201     onExpand: function() {
99202         var me = this,
99203             keyNav = me.listKeyNav,
99204             selectOnTab = me.selectOnTab,
99205             picker = me.getPicker();
99206
99207         // Handle BoundList navigation from the input field. Insert a tab listener specially to enable selectOnTab.
99208         if (keyNav) {
99209             keyNav.enable();
99210         } else {
99211             keyNav = me.listKeyNav = Ext.create('Ext.view.BoundListKeyNav', this.inputEl, {
99212                 boundList: picker,
99213                 forceKeyDown: true,
99214                 tab: function(e) {
99215                     if (selectOnTab) {
99216                         this.selectHighlighted(e);
99217                         me.triggerBlur();
99218                     }
99219                     // Tab key event is allowed to propagate to field
99220                     return true;
99221                 }
99222             });
99223         }
99224
99225         // While list is expanded, stop tab monitoring from Ext.form.field.Trigger so it doesn't short-circuit selectOnTab
99226         if (selectOnTab) {
99227             me.ignoreMonitorTab = true;
99228         }
99229
99230         Ext.defer(keyNav.enable, 1, keyNav); //wait a bit so it doesn't react to the down arrow opening the picker
99231         me.inputEl.focus();
99232     },
99233
99234     /**
99235      * @private
99236      * Disables the key nav for the BoundList when it is collapsed.
99237      */
99238     onCollapse: function() {
99239         var me = this,
99240             keyNav = me.listKeyNav;
99241         if (keyNav) {
99242             keyNav.disable();
99243             me.ignoreMonitorTab = false;
99244         }
99245     },
99246
99247     /**
99248      * Selects an item by a {@link Ext.data.Model Model}, or by a key value.
99249      * @param r
99250      */
99251     select: function(r) {
99252         this.setValue(r, true);
99253     },
99254
99255     /**
99256      * Find the record by searching for a specific field/value combination
99257      * Returns an Ext.data.Record or false
99258      * @private
99259      */
99260     findRecord: function(field, value) {
99261         var ds = this.store,
99262             idx = ds.findExact(field, value);
99263         return idx !== -1 ? ds.getAt(idx) : false;
99264     },
99265     findRecordByValue: function(value) {
99266         return this.findRecord(this.valueField, value);
99267     },
99268     findRecordByDisplay: function(value) {
99269         return this.findRecord(this.displayField, value);
99270     },
99271
99272     /**
99273      * Sets the specified value(s) into the field. For each value, if a record is found in the {@link #store} that
99274      * matches based on the {@link #valueField}, then that record's {@link #displayField} will be displayed in the
99275      * field.  If no match is found, and the {@link #valueNotFoundText} config option is defined, then that will be
99276      * displayed as the default field text. Otherwise a blank value will be shown, although the value will still be set.
99277      * @param {String|Array} value The value(s) to be set. Can be either a single String or {@link Ext.data.Model},
99278      * or an Array of Strings or Models.
99279      * @return {Ext.form.field.Field} this
99280      */
99281     setValue: function(value, doSelect) {
99282         var me = this,
99283             valueNotFoundText = me.valueNotFoundText,
99284             inputEl = me.inputEl,
99285             i, len, record,
99286             models = [],
99287             displayTplData = [],
99288             processedValue = [];
99289
99290         if (me.store.loading) {
99291             // Called while the Store is loading. Ensure it is processed by the onLoad method.
99292             me.value = value;
99293             return me;
99294         }
99295
99296         // This method processes multi-values, so ensure value is an array.
99297         value = Ext.Array.from(value);
99298
99299         // Loop through values
99300         for (i = 0, len = value.length; i < len; i++) {
99301             record = value[i];
99302             if (!record || !record.isModel) {
99303                 record = me.findRecordByValue(record);
99304             }
99305             // record found, select it.
99306             if (record) {
99307                 models.push(record);
99308                 displayTplData.push(record.data);
99309                 processedValue.push(record.get(me.valueField));
99310             }
99311             // record was not found, this could happen because
99312             // store is not loaded or they set a value not in the store
99313             else {
99314                 // if valueNotFoundText is defined, display it, otherwise display nothing for this value
99315                 if (Ext.isDefined(valueNotFoundText)) {
99316                     displayTplData.push(valueNotFoundText);
99317                 }
99318                 processedValue.push(value[i]);
99319             }
99320         }
99321
99322         // Set the value of this field. If we are multiselecting, then that is an array.
99323         me.value = me.multiSelect ? processedValue : processedValue[0];
99324         if (!Ext.isDefined(me.value)) {
99325             me.value = null;
99326         }
99327         me.displayTplData = displayTplData; //store for getDisplayValue method
99328         me.lastSelection = me.valueModels = models;
99329
99330         if (inputEl && me.emptyText && !Ext.isEmpty(value)) {
99331             inputEl.removeCls(me.emptyCls);
99332         }
99333
99334         // Calculate raw value from the collection of Model data
99335         me.setRawValue(me.getDisplayValue());
99336         me.checkChange();
99337
99338         if (doSelect !== false) {
99339             me.syncSelection();
99340         }
99341         me.applyEmptyText();
99342
99343         return me;
99344     },
99345
99346     /**
99347      * @private Generate the string value to be displayed in the text field for the currently stored value
99348      */
99349     getDisplayValue: function() {
99350         return this.displayTpl.apply(this.displayTplData);
99351     },
99352
99353     getValue: function() {
99354         // If the user has not changed the raw field value since a value was selected from the list,
99355         // then return the structured value from the selection. If the raw field value is different
99356         // than what would be displayed due to selection, return that raw value.
99357         var me = this,
99358             picker = me.picker,
99359             rawValue = me.getRawValue(), //current value of text field
99360             value = me.value; //stored value from last selection or setValue() call
99361
99362         if (me.getDisplayValue() !== rawValue) {
99363             value = rawValue;
99364             me.value = me.displayTplData = me.valueModels = null;
99365             if (picker) {
99366                 me.ignoreSelection++;
99367                 picker.getSelectionModel().deselectAll();
99368                 me.ignoreSelection--;
99369             }
99370         }
99371
99372         return value;
99373     },
99374
99375     getSubmitValue: function() {
99376         return this.getValue();
99377     },
99378
99379     isEqual: function(v1, v2) {
99380         var fromArray = Ext.Array.from,
99381             i, len;
99382
99383         v1 = fromArray(v1);
99384         v2 = fromArray(v2);
99385         len = v1.length;
99386
99387         if (len !== v2.length) {
99388             return false;
99389         }
99390
99391         for(i = 0; i < len; i++) {
99392             if (v2[i] !== v1[i]) {
99393                 return false;
99394             }
99395         }
99396
99397         return true;
99398     },
99399
99400     /**
99401      * Clears any value currently set in the ComboBox.
99402      */
99403     clearValue: function() {
99404         this.setValue([]);
99405     },
99406
99407     /**
99408      * @private Synchronizes the selection in the picker to match the current value of the combobox.
99409      */
99410     syncSelection: function() {
99411         var me = this,
99412             ExtArray = Ext.Array,
99413             picker = me.picker,
99414             selection, selModel;
99415         if (picker) {
99416             // From the value, find the Models that are in the store's current data
99417             selection = [];
99418             ExtArray.forEach(me.valueModels || [], function(value) {
99419                 if (value && value.isModel && me.store.indexOf(value) >= 0) {
99420                     selection.push(value);
99421                 }
99422             });
99423
99424             // Update the selection to match
99425             me.ignoreSelection++;
99426             selModel = picker.getSelectionModel();
99427             selModel.deselectAll();
99428             if (selection.length) {
99429                 selModel.select(selection);
99430             }
99431             me.ignoreSelection--;
99432         }
99433     }
99434 });
99435
99436 /**
99437  * @private
99438  * @class Ext.picker.Month
99439  * @extends Ext.Component
99440  * <p>A month picker component. This class is used by the {@link Ext.picker.Date DatePicker} class
99441  * to allow browsing and selection of year/months combinations.</p>
99442  * @constructor
99443  * Create a new MonthPicker
99444  * @param {Object} config The config object
99445  * @xtype monthpicker
99446  * @private
99447  */
99448 Ext.define('Ext.picker.Month', {
99449     extend: 'Ext.Component',
99450     requires: ['Ext.XTemplate', 'Ext.util.ClickRepeater', 'Ext.Date', 'Ext.button.Button'],
99451     alias: 'widget.monthpicker',
99452     alternateClassName: 'Ext.MonthPicker',
99453
99454     renderTpl: [
99455         '<div class="{baseCls}-body">',
99456           '<div class="{baseCls}-months">',
99457               '<tpl for="months">',
99458                   '<div class="{parent.baseCls}-item {parent.baseCls}-month"><a href="#" hidefocus="on">{.}</a></div>',
99459               '</tpl>',
99460           '</div>',
99461           '<div class="{baseCls}-years">',
99462               '<div class="{baseCls}-yearnav">',
99463                   '<button class="{baseCls}-yearnav-prev"></button>',
99464                   '<button class="{baseCls}-yearnav-next"></button>',
99465               '</div>',
99466               '<tpl for="years">',
99467                   '<div class="{parent.baseCls}-item {parent.baseCls}-year"><a href="#" hidefocus="on">{.}</a></div>',
99468               '</tpl>',
99469           '</div>',
99470         '</div>',
99471         '<div class="' + Ext.baseCSSPrefix + 'clear"></div>',
99472         '<tpl if="showButtons">',
99473           '<div class="{baseCls}-buttons"></div>',
99474         '</tpl>'
99475     ],
99476
99477     /**
99478      * @cfg {String} okText The text to display on the ok button. Defaults to <tt>'OK'</tt>
99479      */
99480     okText: 'OK',
99481
99482     /**
99483      * @cfg {String} cancelText The text to display on the cancel button. Defaults to <tt>'Cancel'</tt>
99484      */
99485     cancelText: 'Cancel',
99486
99487     /**
99488      * @cfg {String} baseCls The base CSS class to apply to the picker element. Defaults to <tt>'x-monthpicker'</tt>
99489      */
99490     baseCls: Ext.baseCSSPrefix + 'monthpicker',
99491
99492     /**
99493      * @cfg {Boolean} showButtons True to show ok and cancel buttons below the picker. Defaults to <tt>true</tt>.
99494      */
99495     showButtons: true,
99496
99497     /**
99498      * @cfg {String} selectedCls The class to be added to selected items in the picker. Defaults to
99499      * <tt>'x-monthpicker-selected'</tt>
99500      */
99501
99502     /**
99503      * @cfg {Date/Array} value The default value to set. See {#setValue setValue}
99504      */
99505
99506     width: 175,
99507
99508     height: 195,
99509
99510
99511     // private
99512     totalYears: 10,
99513     yearOffset: 5, // 10 years in total, 2 per row
99514     monthOffset: 6, // 12 months, 2 per row
99515
99516     // private, inherit docs
99517     initComponent: function(){
99518         var me = this;
99519
99520         me.selectedCls = me.baseCls + '-selected';
99521         me.addEvents(
99522             /**
99523              * @event cancelclick
99524              * Fires when the cancel button is pressed.
99525              * @param {Ext.picker.Month} this
99526              */
99527             'cancelclick',
99528
99529             /**
99530              * @event monthclick
99531              * Fires when a month is clicked.
99532              * @param {Ext.picker.Month} this
99533              * @param {Array} value The current value
99534              */
99535             'monthclick',
99536
99537             /**
99538              * @event monthdblclick
99539              * Fires when a month is clicked.
99540              * @param {Ext.picker.Month} this
99541              * @param {Array} value The current value
99542              */
99543             'monthdblclick',
99544
99545             /**
99546              * @event okclick
99547              * Fires when the ok button is pressed.
99548              * @param {Ext.picker.Month} this
99549              * @param {Array} value The current value
99550              */
99551             'okclick',
99552
99553             /**
99554              * @event select
99555              * Fires when a month/year is selected.
99556              * @param {Ext.picker.Month} this
99557              * @param {Array} value The current value
99558              */
99559             'select',
99560
99561             /**
99562              * @event yearclick
99563              * Fires when a year is clicked.
99564              * @param {Ext.picker.Month} this
99565              * @param {Array} value The current value
99566              */
99567             'yearclick',
99568
99569             /**
99570              * @event yeardblclick
99571              * Fires when a year is clicked.
99572              * @param {Ext.picker.Month} this
99573              * @param {Array} value The current value
99574              */
99575             'yeardblclick'
99576         );
99577
99578         me.setValue(me.value);
99579         me.activeYear = me.getYear(new Date().getFullYear() - 4, -4);
99580         this.callParent();
99581     },
99582
99583     // private, inherit docs
99584     onRender: function(ct, position){
99585         var me = this,
99586             i = 0,
99587             months = [],
99588             shortName = Ext.Date.getShortMonthName,
99589             monthLen = me.monthOffset;
99590
99591         for (; i < monthLen; ++i) {
99592             months.push(shortName(i), shortName(i + monthLen));
99593         }
99594
99595         Ext.apply(me.renderData, {
99596             months: months,
99597             years: me.getYears(),
99598             showButtons: me.showButtons
99599         });
99600
99601         Ext.apply(me.renderSelectors, {
99602             bodyEl: '.' + me.baseCls + '-body',
99603             prevEl: '.' + me.baseCls + '-yearnav-prev',
99604             nextEl: '.' + me.baseCls + '-yearnav-next',
99605             buttonsEl: '.' + me.baseCls + '-buttons'
99606         });
99607         this.callParent([ct, position]);
99608     },
99609
99610     // private, inherit docs
99611     afterRender: function(){
99612         var me = this,
99613             body = me.bodyEl,
99614             buttonsEl = me.buttonsEl;
99615
99616         me.callParent();
99617
99618         me.mon(body, 'click', me.onBodyClick, me);
99619         me.mon(body, 'dblclick', me.onBodyClick, me);
99620
99621         // keep a reference to the year/month elements since we'll be re-using them
99622         me.years = body.select('.' + me.baseCls + '-year a');
99623         me.months = body.select('.' + me.baseCls + '-month a');
99624
99625         if (me.showButtons) {
99626             me.okBtn = Ext.create('Ext.button.Button', {
99627                 text: me.okText,
99628                 renderTo: buttonsEl,
99629                 handler: me.onOkClick,
99630                 scope: me
99631             });
99632             me.cancelBtn = Ext.create('Ext.button.Button', {
99633                 text: me.cancelText,
99634                 renderTo: buttonsEl,
99635                 handler: me.onCancelClick,
99636                 scope: me
99637             });
99638         }
99639
99640         me.backRepeater = Ext.create('Ext.util.ClickRepeater', me.prevEl, {
99641             handler: Ext.Function.bind(me.adjustYear, me, [-me.totalYears])
99642         });
99643
99644         me.prevEl.addClsOnOver(me.baseCls + '-yearnav-prev-over');
99645         me.nextRepeater = Ext.create('Ext.util.ClickRepeater', me.nextEl, {
99646             handler: Ext.Function.bind(me.adjustYear, me, [me.totalYears])
99647         });
99648         me.nextEl.addClsOnOver(me.baseCls + '-yearnav-next-over');
99649         me.updateBody();
99650     },
99651
99652     /**
99653      * Set the value for the picker.
99654      * @param {Date/Array} value The value to set. It can be a Date object, where the month/year will be extracted, or
99655      * it can be an array, with the month as the first index and the year as the second.
99656      * @return {Ext.picker.Month} this
99657      */
99658     setValue: function(value){
99659         var me = this,
99660             active = me.activeYear,
99661             offset = me.monthOffset,
99662             year,
99663             index;
99664
99665         if (!value) {
99666             me.value = [null, null];
99667         } else if (Ext.isDate(value)) {
99668             me.value = [value.getMonth(), value.getFullYear()];
99669         } else {
99670             me.value = [value[0], value[1]];
99671         }
99672
99673         if (me.rendered) {
99674             year = me.value[1];
99675             if (year !== null) {
99676                 if ((year < active || year > active + me.yearOffset)) {
99677                     me.activeYear = year - me.yearOffset + 1;
99678                 }
99679             }
99680             me.updateBody();
99681         }
99682
99683         return me;
99684     },
99685
99686     /**
99687      * Gets the selected value. It is returned as an array [month, year]. It may
99688      * be a partial value, for example [null, 2010]. The month is returned as
99689      * 0 based.
99690      * @return {Array} The selected value
99691      */
99692     getValue: function(){
99693         return this.value;
99694     },
99695
99696     /**
99697      * Checks whether the picker has a selection
99698      * @return {Boolean} Returns true if both a month and year have been selected
99699      */
99700     hasSelection: function(){
99701         var value = this.value;
99702         return value[0] !== null && value[1] !== null;
99703     },
99704
99705     /**
99706      * Get an array of years to be pushed in the template. It is not in strict
99707      * numerical order because we want to show them in columns.
99708      * @private
99709      * @return {Array} An array of years
99710      */
99711     getYears: function(){
99712         var me = this,
99713             offset = me.yearOffset,
99714             start = me.activeYear, // put the "active" year on the left
99715             end = start + offset,
99716             i = start,
99717             years = [];
99718
99719         for (; i < end; ++i) {
99720             years.push(i, i + offset);
99721         }
99722
99723         return years;
99724     },
99725
99726     /**
99727      * Update the years in the body based on any change
99728      * @private
99729      */
99730     updateBody: function(){
99731         var me = this,
99732             years = me.years,
99733             months = me.months,
99734             yearNumbers = me.getYears(),
99735             cls = me.selectedCls,
99736             value = me.getYear(null),
99737             month = me.value[0],
99738             monthOffset = me.monthOffset,
99739             year;
99740
99741         if (me.rendered) {
99742             years.removeCls(cls);
99743             months.removeCls(cls);
99744             years.each(function(el, all, index){
99745                 year = yearNumbers[index];
99746                 el.dom.innerHTML = year;
99747                 if (year == value) {
99748                     el.dom.className = cls;
99749                 }
99750             });
99751             if (month !== null) {
99752                 if (month < monthOffset) {
99753                     month = month * 2;
99754                 } else {
99755                     month = (month - monthOffset) * 2 + 1;
99756                 }
99757                 months.item(month).addCls(cls);
99758             }
99759         }
99760     },
99761
99762     /**
99763      * Gets the current year value, or the default.
99764      * @private
99765      * @param {Number} defaultValue The default value to use if the year is not defined.
99766      * @param {Number} offset A number to offset the value by
99767      * @return {Number} The year value
99768      */
99769     getYear: function(defaultValue, offset) {
99770         var year = this.value[1];
99771         offset = offset || 0;
99772         return year === null ? defaultValue : year + offset;
99773     },
99774
99775     /**
99776      * React to clicks on the body
99777      * @private
99778      */
99779     onBodyClick: function(e, t) {
99780         var me = this,
99781             isDouble = e.type == 'dblclick';
99782
99783         if (e.getTarget('.' + me.baseCls + '-month')) {
99784             e.stopEvent();
99785             me.onMonthClick(t, isDouble);
99786         } else if (e.getTarget('.' + me.baseCls + '-year')) {
99787             e.stopEvent();
99788             me.onYearClick(t, isDouble);
99789         }
99790     },
99791
99792     /**
99793      * Modify the year display by passing an offset.
99794      * @param {Number} offset The offset to move by. If not specified, it defaults to 10.
99795      */
99796     adjustYear: function(offset){
99797         if (typeof offset != 'number') {
99798             offset = this.totalYears;
99799         }
99800         this.activeYear += offset;
99801         this.updateBody();
99802     },
99803
99804     /**
99805      * React to the ok button being pressed
99806      * @private
99807      */
99808     onOkClick: function(){
99809         this.fireEvent('okclick', this, this.value);
99810     },
99811
99812     /**
99813      * React to the cancel button being pressed
99814      * @private
99815      */
99816     onCancelClick: function(){
99817         this.fireEvent('cancelclick', this);
99818     },
99819
99820     /**
99821      * React to a month being clicked
99822      * @private
99823      * @param {HTMLElement} target The element that was clicked
99824      * @param {Boolean} isDouble True if the event was a doubleclick
99825      */
99826     onMonthClick: function(target, isDouble){
99827         var me = this;
99828         me.value[0] = me.resolveOffset(me.months.indexOf(target), me.monthOffset);
99829         me.updateBody();
99830         me.fireEvent('month' + (isDouble ? 'dbl' : '') + 'click', me, me.value);
99831         me.fireEvent('select', me, me.value);
99832     },
99833
99834     /**
99835      * React to a year being clicked
99836      * @private
99837      * @param {HTMLElement} target The element that was clicked
99838      * @param {Boolean} isDouble True if the event was a doubleclick
99839      */
99840     onYearClick: function(target, isDouble){
99841         var me = this;
99842         me.value[1] = me.activeYear + me.resolveOffset(me.years.indexOf(target), me.yearOffset);
99843         me.updateBody();
99844         me.fireEvent('year' + (isDouble ? 'dbl' : '') + 'click', me, me.value);
99845         me.fireEvent('select', me, me.value);
99846
99847     },
99848
99849     /**
99850      * Returns an offsetted number based on the position in the collection. Since our collections aren't
99851      * numerically ordered, this function helps to normalize those differences.
99852      * @private
99853      * @param {Object} index
99854      * @param {Object} offset
99855      * @return {Number} The correctly offsetted number
99856      */
99857     resolveOffset: function(index, offset){
99858         if (index % 2 === 0) {
99859             return (index / 2);
99860         } else {
99861             return offset + Math.floor(index / 2);
99862         }
99863     },
99864
99865     // private, inherit docs
99866     beforeDestroy: function(){
99867         var me = this;
99868         me.years = me.months = null;
99869         Ext.destroyMembers('backRepeater', 'nextRepeater', 'okBtn', 'cancelBtn');
99870         this.callParent();
99871     }
99872 });
99873
99874 /**
99875  * @class Ext.picker.Date
99876  * @extends Ext.Component
99877  * <p>A date picker. This class is used by the {@link Ext.form.field.Date} field to allow browsing and
99878  * selection of valid dates in a popup next to the field, but may also be used with other components.</p>
99879  * <p>Typically you will need to implement a handler function to be notified when the user chooses a color from the
99880  * picker; you can register the handler using the {@link #select} event, or by implementing the {@link #handler}
99881  * method.</p>
99882  * <p>By default the user will be allowed to pick any date; this can be changed by using the {@link #minDate},
99883  * {@link #maxDate}, {@link #disabledDays}, {@link #disabledDatesRE}, and/or {@link #disabledDates} configs.</p>
99884  * <p>All the string values documented below may be overridden by including an Ext locale file in your page.</p>
99885  * <p>Example usage:</p>
99886  * <pre><code>new Ext.panel.Panel({
99887     title: 'Choose a future date:',
99888     width: 200,
99889     bodyPadding: 10,
99890     renderTo: Ext.getBody(),
99891     items: [{
99892         xtype: 'datepicker',
99893         minDate: new Date(),
99894         handler: function(picker, date) {
99895             // do something with the selected date
99896         }
99897     }]
99898 });</code></pre>
99899  * {@img Ext.picker.Date/Ext.picker.Date.png Ext.picker.Date component}
99900  *
99901  * @constructor
99902  * Create a new DatePicker
99903  * @param {Object} config The config object
99904  *
99905  * @xtype datepicker
99906  */
99907 Ext.define('Ext.picker.Date', {
99908     extend: 'Ext.Component',
99909     requires: [
99910         'Ext.XTemplate',
99911         'Ext.button.Button',
99912         'Ext.button.Split',
99913         'Ext.util.ClickRepeater',
99914         'Ext.util.KeyNav',
99915         'Ext.EventObject',
99916         'Ext.fx.Manager',
99917         'Ext.picker.Month'
99918     ],
99919     alias: 'widget.datepicker',
99920     alternateClassName: 'Ext.DatePicker',
99921
99922     renderTpl: [
99923         '<div class="{cls}" id="{id}" role="grid" title="{ariaTitle} {value:this.longDay}">',
99924             '<div role="presentation" class="{baseCls}-header">',
99925                 '<div class="{baseCls}-prev"><a href="#" role="button" title="{prevText}"></a></div>',
99926                 '<div class="{baseCls}-month"></div>',
99927                 '<div class="{baseCls}-next"><a href="#" role="button" title="{nextText}"></a></div>',
99928             '</div>',
99929             '<table class="{baseCls}-inner" cellspacing="0" role="presentation">',
99930                 '<thead role="presentation"><tr role="presentation">',
99931                     '<tpl for="dayNames">',
99932                         '<th role="columnheader" title="{.}"><span>{.:this.firstInitial}</span></th>',
99933                     '</tpl>',
99934                 '</tr></thead>',
99935                 '<tbody role="presentation"><tr role="presentation">',
99936                     '<tpl for="days">',
99937                         '{#:this.isEndOfWeek}',
99938                         '<td role="gridcell" id="{[Ext.id()]}">',
99939                             '<a role="presentation" href="#" hidefocus="on" class="{parent.baseCls}-date" tabIndex="1">',
99940                                 '<em role="presentation"><span role="presentation"></span></em>',
99941                             '</a>',
99942                         '</td>',
99943                     '</tpl>',
99944                 '</tr></tbody>',
99945             '</table>',
99946             '<tpl if="showToday">',
99947                 '<div role="presentation" class="{baseCls}-footer"></div>',
99948             '</tpl>',
99949         '</div>',
99950         {
99951             firstInitial: function(value) {
99952                 return value.substr(0,1);
99953             },
99954             isEndOfWeek: function(value) {
99955                 // convert from 1 based index to 0 based
99956                 // by decrementing value once.
99957                 value--;
99958                 var end = value % 7 === 0 && value !== 0;
99959                 return end ? '</tr><tr role="row">' : '';
99960             },
99961             longDay: function(value){
99962                 return Ext.Date.format(value, this.longDayFormat);
99963             }
99964         }
99965     ],
99966
99967     ariaTitle: 'Date Picker',
99968     /**
99969      * @cfg {String} todayText
99970      * The text to display on the button that selects the current date (defaults to <code>'Today'</code>)
99971      */
99972     todayText : 'Today',
99973     /**
99974      * @cfg {Function} handler
99975      * Optional. A function that will handle the select event of this picker.
99976      * The handler is passed the following parameters:<div class="mdetail-params"><ul>
99977      * <li><code>picker</code> : Ext.picker.Date <div class="sub-desc">This Date picker.</div></li>
99978      * <li><code>date</code> : Date <div class="sub-desc">The selected date.</div></li>
99979      * </ul></div>
99980      */
99981     /**
99982      * @cfg {Object} scope
99983      * The scope (<code><b>this</b></code> reference) in which the <code>{@link #handler}</code>
99984      * function will be called.  Defaults to this DatePicker instance.
99985      */
99986     /**
99987      * @cfg {String} todayTip
99988      * A string used to format the message for displaying in a tooltip over the button that
99989      * selects the current date. Defaults to <code>'{0} (Spacebar)'</code> where
99990      * the <code>{0}</code> token is replaced by today's date.
99991      */
99992     todayTip : '{0} (Spacebar)',
99993     /**
99994      * @cfg {String} minText
99995      * The error text to display if the minDate validation fails (defaults to <code>'This date is before the minimum date'</code>)
99996      */
99997     minText : 'This date is before the minimum date',
99998     /**
99999      * @cfg {String} maxText
100000      * The error text to display if the maxDate validation fails (defaults to <code>'This date is after the maximum date'</code>)
100001      */
100002     maxText : 'This date is after the maximum date',
100003     /**
100004      * @cfg {String} format
100005      * The default date format string which can be overriden for localization support.  The format must be
100006      * valid according to {@link Ext.Date#parse} (defaults to {@link Ext.Date#defaultFormat}).
100007      */
100008     /**
100009      * @cfg {String} disabledDaysText
100010      * The tooltip to display when the date falls on a disabled day (defaults to <code>'Disabled'</code>)
100011      */
100012     disabledDaysText : 'Disabled',
100013     /**
100014      * @cfg {String} disabledDatesText
100015      * The tooltip text to display when the date falls on a disabled date (defaults to <code>'Disabled'</code>)
100016      */
100017     disabledDatesText : 'Disabled',
100018     /**
100019      * @cfg {Array} monthNames
100020      * An array of textual month names which can be overriden for localization support (defaults to Ext.Date.monthNames)
100021      */
100022     /**
100023      * @cfg {Array} dayNames
100024      * An array of textual day names which can be overriden for localization support (defaults to Ext.Date.dayNames)
100025      */
100026     /**
100027      * @cfg {String} nextText
100028      * The next month navigation button tooltip (defaults to <code>'Next Month (Control+Right)'</code>)
100029      */
100030     nextText : 'Next Month (Control+Right)',
100031     /**
100032      * @cfg {String} prevText
100033      * The previous month navigation button tooltip (defaults to <code>'Previous Month (Control+Left)'</code>)
100034      */
100035     prevText : 'Previous Month (Control+Left)',
100036     /**
100037      * @cfg {String} monthYearText
100038      * The header month selector tooltip (defaults to <code>'Choose a month (Control+Up/Down to move years)'</code>)
100039      */
100040     monthYearText : 'Choose a month (Control+Up/Down to move years)',
100041     /**
100042      * @cfg {Number} startDay
100043      * Day index at which the week should begin, 0-based (defaults to 0, which is Sunday)
100044      */
100045     startDay : 0,
100046     /**
100047      * @cfg {Boolean} showToday
100048      * False to hide the footer area containing the Today button and disable the keyboard handler for spacebar
100049      * that selects the current date (defaults to <code>true</code>).
100050      */
100051     showToday : true,
100052     /**
100053      * @cfg {Date} minDate
100054      * Minimum allowable date (JavaScript date object, defaults to null)
100055      */
100056     /**
100057      * @cfg {Date} maxDate
100058      * Maximum allowable date (JavaScript date object, defaults to null)
100059      */
100060     /**
100061      * @cfg {Array} disabledDays
100062      * An array of days to disable, 0-based. For example, [0, 6] disables Sunday and Saturday (defaults to null).
100063      */
100064     /**
100065      * @cfg {RegExp} disabledDatesRE
100066      * JavaScript regular expression used to disable a pattern of dates (defaults to null).  The {@link #disabledDates}
100067      * config will generate this regex internally, but if you specify disabledDatesRE it will take precedence over the
100068      * disabledDates value.
100069      */
100070     /**
100071      * @cfg {Array} disabledDates
100072      * An array of 'dates' to disable, as strings. These strings will be used to build a dynamic regular
100073      * expression so they are very powerful. Some examples:
100074      * <ul>
100075      * <li>['03/08/2003', '09/16/2003'] would disable those exact dates</li>
100076      * <li>['03/08', '09/16'] would disable those days for every year</li>
100077      * <li>['^03/08'] would only match the beginning (useful if you are using short years)</li>
100078      * <li>['03/../2006'] would disable every day in March 2006</li>
100079      * <li>['^03'] would disable every day in every March</li>
100080      * </ul>
100081      * Note that the format of the dates included in the array should exactly match the {@link #format} config.
100082      * In order to support regular expressions, if you are using a date format that has '.' in it, you will have to
100083      * escape the dot when restricting dates. For example: ['03\\.08\\.03'].
100084      */
100085
100086     /**
100087      * @cfg {Boolean} disableAnim True to disable animations when showing the month picker. Defaults to <tt>false</tt>.
100088      */
100089     disableAnim: true,
100090
100091     /**
100092      * @cfg {String} baseCls
100093      * The base CSS class to apply to this components element (defaults to <tt>'x-datepicker'</tt>).
100094      */
100095     baseCls: Ext.baseCSSPrefix + 'datepicker',
100096
100097     /**
100098      * @cfg {String} selectedCls
100099      * The class to apply to the selected cell. Defaults to <tt>'x-datepicker-selected'</tt>
100100      */
100101
100102     /**
100103      * @cfg {String} disabledCellCls
100104      * The class to apply to disabled cells. Defaults to <tt>'x-datepicker-disabled'</tt>
100105      */
100106
100107     /**
100108      * @cfg {String} longDayFormat
100109      * The format for displaying a date in a longer format. Defaults to <tt>'F d, Y'</tt>
100110      */
100111     longDayFormat: 'F d, Y',
100112
100113     /**
100114      * @cfg {Object} keyNavConfig Specifies optional custom key event handlers for the {@link Ext.util.KeyNav}
100115      * attached to this date picker. Must conform to the config format recognized by the {@link Ext.util.KeyNav}
100116      * constructor. Handlers specified in this object will replace default handlers of the same name.
100117      */
100118
100119     /**
100120      * @cfg {Boolean} focusOnShow
100121      * True to automatically focus the picker on show. Defaults to <tt>false</tt>.
100122      */
100123     focusOnShow: false,
100124
100125     // private
100126     // Set by other components to stop the picker focus being updated when the value changes.
100127     focusOnSelect: true,
100128
100129     width: 178,
100130
100131     // default value used to initialise each date in the DatePicker
100132     // (note: 12 noon was chosen because it steers well clear of all DST timezone changes)
100133     initHour: 12, // 24-hour format
100134
100135     numDays: 42,
100136
100137     // private, inherit docs
100138     initComponent : function() {
100139         var me = this,
100140             clearTime = Ext.Date.clearTime;
100141
100142         me.selectedCls = me.baseCls + '-selected';
100143         me.disabledCellCls = me.baseCls + '-disabled';
100144         me.prevCls = me.baseCls + '-prevday';
100145         me.activeCls = me.baseCls + '-active';
100146         me.nextCls = me.baseCls + '-prevday';
100147         me.todayCls = me.baseCls + '-today';
100148         me.dayNames = me.dayNames.slice(me.startDay).concat(me.dayNames.slice(0, me.startDay));
100149         this.callParent();
100150
100151         me.value = me.value ?
100152                  clearTime(me.value, true) : clearTime(new Date());
100153
100154         me.addEvents(
100155             /**
100156              * @event select
100157              * Fires when a date is selected
100158              * @param {DatePicker} this DatePicker
100159              * @param {Date} date The selected date
100160              */
100161             'select'
100162         );
100163
100164         me.initDisabledDays();
100165     },
100166
100167     // private, inherit docs
100168     onRender : function(container, position){
100169         /*
100170          * days array for looping through 6 full weeks (6 weeks * 7 days)
100171          * Note that we explicitly force the size here so the template creates
100172          * all the appropriate cells.
100173          */
100174
100175         var me = this,
100176             days = new Array(me.numDays),
100177             today = Ext.Date.format(new Date(), me.format);
100178
100179         Ext.applyIf(me, {
100180             renderData: {},
100181             renderSelectors: {}
100182         });
100183
100184         Ext.apply(me.renderData, {
100185             dayNames: me.dayNames,
100186             ariaTitle: me.ariaTitle,
100187             value: me.value,
100188             showToday: me.showToday,
100189             prevText: me.prevText,
100190             nextText: me.nextText,
100191             days: days
100192         });
100193         me.getTpl('renderTpl').longDayFormat = me.longDayFormat;
100194
100195         Ext.apply(me.renderSelectors, {
100196             eventEl: 'table.' + me.baseCls + '-inner',
100197             prevEl: '.' + me.baseCls + '-prev a',
100198             nextEl: '.' + me.baseCls + '-next a',
100199             middleBtnEl: '.' + me.baseCls + '-month',
100200             footerEl: '.' + me.baseCls + '-footer'
100201         });
100202
100203         this.callParent(arguments);
100204         me.el.unselectable();
100205
100206         me.cells = me.eventEl.select('tbody td');
100207         me.textNodes = me.eventEl.query('tbody td span');
100208
100209         me.monthBtn = Ext.create('Ext.button.Split', {
100210             text: '',
100211             tooltip: me.monthYearText,
100212             renderTo: me.middleBtnEl
100213         });
100214         //~ me.middleBtnEl.down('button').addCls(Ext.baseCSSPrefix + 'btn-arrow');
100215
100216
100217         me.todayBtn = Ext.create('Ext.button.Button', {
100218             renderTo: me.footerEl,
100219             text: Ext.String.format(me.todayText, today),
100220             tooltip: Ext.String.format(me.todayTip, today),
100221             handler: me.selectToday,
100222             scope: me
100223         });
100224     },
100225
100226     // private, inherit docs
100227     initEvents: function(){
100228         var me = this,
100229             eDate = Ext.Date,
100230             day = eDate.DAY;
100231
100232         this.callParent();
100233
100234         me.prevRepeater = Ext.create('Ext.util.ClickRepeater', me.prevEl, {
100235             handler: me.showPrevMonth,
100236             scope: me,
100237             preventDefault: true,
100238             stopDefault: true
100239         });
100240
100241         me.nextRepeater = Ext.create('Ext.util.ClickRepeater', me.nextEl, {
100242             handler: me.showNextMonth,
100243             scope: me,
100244             preventDefault:true,
100245             stopDefault:true
100246         });
100247
100248         me.keyNav = Ext.create('Ext.util.KeyNav', me.eventEl, Ext.apply({
100249             scope: me,
100250             'left' : function(e){
100251                 if(e.ctrlKey){
100252                     me.showPrevMonth();
100253                 }else{
100254                     me.update(eDate.add(me.activeDate, day, -1));
100255                 }
100256             },
100257
100258             'right' : function(e){
100259                 if(e.ctrlKey){
100260                     me.showNextMonth();
100261                 }else{
100262                     me.update(eDate.add(me.activeDate, day, 1));
100263                 }
100264             },
100265
100266             'up' : function(e){
100267                 if(e.ctrlKey){
100268                     me.showNextYear();
100269                 }else{
100270                     me.update(eDate.add(me.activeDate, day, -7));
100271                 }
100272             },
100273
100274             'down' : function(e){
100275                 if(e.ctrlKey){
100276                     me.showPrevYear();
100277                 }else{
100278                     me.update(eDate.add(me.activeDate, day, 7));
100279                 }
100280             },
100281             'pageUp' : me.showNextMonth,
100282             'pageDown' : me.showPrevMonth,
100283             'enter' : function(e){
100284                 e.stopPropagation();
100285                 return true;
100286             }
100287         }, me.keyNavConfig));
100288
100289         if(me.showToday){
100290             me.todayKeyListener = me.eventEl.addKeyListener(Ext.EventObject.SPACE, me.selectToday,  me);
100291         }
100292         me.mon(me.eventEl, 'mousewheel', me.handleMouseWheel, me);
100293         me.mon(me.eventEl, 'click', me.handleDateClick,  me, {delegate: 'a.' + me.baseCls + '-date'});
100294         me.mon(me.monthBtn, 'click', me.showMonthPicker, me);
100295         me.mon(me.monthBtn, 'arrowclick', me.showMonthPicker, me);
100296         me.update(me.value);
100297     },
100298
100299     /**
100300      * Setup the disabled dates regex based on config options
100301      * @private
100302      */
100303     initDisabledDays : function(){
100304         var me = this,
100305             dd = me.disabledDates,
100306             re = '(?:',
100307             len;
100308
100309         if(!me.disabledDatesRE && dd){
100310                 len = dd.length - 1;
100311
100312             Ext.each(dd, function(d, i){
100313                 re += Ext.isDate(d) ? '^' + Ext.String.escapeRegex(Ext.Date.dateFormat(d, me.format)) + '$' : dd[i];
100314                 if(i != len){
100315                     re += '|';
100316                 }
100317             }, me);
100318             me.disabledDatesRE = new RegExp(re + ')');
100319         }
100320     },
100321
100322     /**
100323      * Replaces any existing disabled dates with new values and refreshes the DatePicker.
100324      * @param {Array/RegExp} disabledDates An array of date strings (see the {@link #disabledDates} config
100325      * for details on supported values), or a JavaScript regular expression used to disable a pattern of dates.
100326      * @return {Ext.picker.Date} this
100327      */
100328     setDisabledDates : function(dd){
100329         var me = this;
100330
100331         if(Ext.isArray(dd)){
100332             me.disabledDates = dd;
100333             me.disabledDatesRE = null;
100334         }else{
100335             me.disabledDatesRE = dd;
100336         }
100337         me.initDisabledDays();
100338         me.update(me.value, true);
100339         return me;
100340     },
100341
100342     /**
100343      * Replaces any existing disabled days (by index, 0-6) with new values and refreshes the DatePicker.
100344      * @param {Array} disabledDays An array of disabled day indexes. See the {@link #disabledDays} config
100345      * for details on supported values.
100346      * @return {Ext.picker.Date} this
100347      */
100348     setDisabledDays : function(dd){
100349         this.disabledDays = dd;
100350         return this.update(this.value, true);
100351     },
100352
100353     /**
100354      * Replaces any existing {@link #minDate} with the new value and refreshes the DatePicker.
100355      * @param {Date} value The minimum date that can be selected
100356      * @return {Ext.picker.Date} this
100357      */
100358     setMinDate : function(dt){
100359         this.minDate = dt;
100360         return this.update(this.value, true);
100361     },
100362
100363     /**
100364      * Replaces any existing {@link #maxDate} with the new value and refreshes the DatePicker.
100365      * @param {Date} value The maximum date that can be selected
100366      * @return {Ext.picker.Date} this
100367      */
100368     setMaxDate : function(dt){
100369         this.maxDate = dt;
100370         return this.update(this.value, true);
100371     },
100372
100373     /**
100374      * Sets the value of the date field
100375      * @param {Date} value The date to set
100376      * @return {Ext.picker.Date} this
100377      */
100378     setValue : function(value){
100379         this.value = Ext.Date.clearTime(value, true);
100380         return this.update(this.value);
100381     },
100382
100383     /**
100384      * Gets the current selected value of the date field
100385      * @return {Date} The selected date
100386      */
100387     getValue : function(){
100388         return this.value;
100389     },
100390
100391     // private
100392     focus : function(){
100393         this.update(this.activeDate);
100394     },
100395
100396     // private, inherit docs
100397     onEnable: function(){
100398         this.callParent();
100399         this.setDisabledStatus(false);
100400         this.update(this.activeDate);
100401
100402     },
100403
100404     // private, inherit docs
100405     onDisable : function(){
100406         this.callParent();
100407         this.setDisabledStatus(true);
100408     },
100409
100410     /**
100411      * Set the disabled state of various internal components
100412      * @private
100413      * @param {Boolean} disabled
100414      */
100415     setDisabledStatus : function(disabled){
100416         var me = this;
100417
100418         me.keyNav.setDisabled(disabled);
100419         me.prevRepeater.setDisabled(disabled);
100420         me.nextRepeater.setDisabled(disabled);
100421         if (me.showToday) {
100422             me.todayKeyListener.setDisabled(disabled);
100423             me.todayBtn.setDisabled(disabled);
100424         }
100425     },
100426
100427     /**
100428      * Get the current active date.
100429      * @private
100430      * @return {Date} The active date
100431      */
100432     getActive: function(){
100433         return this.activeDate || me.value;
100434     },
100435
100436     /**
100437      * Run any animation required to hide/show the month picker.
100438      * @private
100439      * @param {Boolean} isHide True if it's a hide operation
100440      */
100441     runAnimation: function(isHide){
100442         var options = {
100443                 target: this.monthPicker,
100444                 duration: 200
100445             };
100446
100447         Ext.fx.Manager.run();
100448         if (isHide) {
100449             //TODO: slideout
100450         } else {
100451             //TODO: slidein
100452         }
100453         Ext.create('Ext.fx.Anim', options);
100454     },
100455
100456     /**
100457      * Hides the month picker, if it's visible.
100458      * @return {Ext.picker.Date} this
100459      */
100460     hideMonthPicker : function(){
100461         var me = this,
100462             picker = me.monthPicker;
100463
100464         if (picker) {
100465             if (me.disableAnim) {
100466                 picker.hide();
100467             } else {
100468                 this.runAnimation(true);
100469             }
100470         }
100471         return me;
100472     },
100473
100474     /**
100475      * Show the month picker
100476      * @return {Ext.picker.Date} this
100477      */
100478     showMonthPicker : function(){
100479
100480         var me = this,
100481             picker,
100482             size,
100483             top,
100484             left;
100485
100486
100487         if (me.rendered && !me.disabled) {
100488             size = me.getSize();
100489             picker = me.createMonthPicker();
100490             picker.show();
100491             picker.setSize(size);
100492             picker.setValue(me.getActive());
100493
100494             if (me.disableAnim) {
100495                 picker.setPosition(-1, -1);
100496             } else {
100497                 me.runAnimation(false);
100498             }
100499         }
100500         return me;
100501     },
100502
100503     /**
100504      * Create the month picker instance
100505      * @private
100506      * @return {Ext.picker.Month} picker
100507      */
100508     createMonthPicker: function(){
100509         var me = this,
100510             picker = me.monthPicker;
100511
100512         if (!picker) {
100513             me.monthPicker = picker = Ext.create('Ext.picker.Month', {
100514                 renderTo: me.el,
100515                 floating: true,
100516                 shadow: false,
100517                 listeners: {
100518                     scope: me,
100519                     cancelclick: me.onCancelClick,
100520                     okclick: me.onOkClick,
100521                     yeardblclick: me.onOkClick,
100522                     monthdblclick: me.onOkClick
100523                 }
100524             });
100525
100526             me.on('beforehide', me.hideMonthPicker, me);
100527         }
100528         return picker;
100529     },
100530
100531     /**
100532      * Respond to an ok click on the month picker
100533      * @private
100534      */
100535     onOkClick: function(picker, value){
100536         var me = this,
100537             month = value[0],
100538             year = value[1],
100539             date = new Date(year, month, me.getActive().getDate());
100540
100541         if (date.getMonth() !== month) {
100542             // 'fix' the JS rolling date conversion if needed
100543             date = new Date(year, month, 1).getLastDateOfMonth();
100544         }
100545         me.update(date);
100546         me.hideMonthPicker();
100547     },
100548
100549     /**
100550      * Respond to a cancel click on the month picker
100551      * @private
100552      */
100553     onCancelClick: function(){
100554         this.hideMonthPicker();
100555     },
100556
100557     /**
100558      * Show the previous month.
100559      * @return {Ext.picker.Date} this
100560      */
100561     showPrevMonth : function(e){
100562         return this.update(Ext.Date.add(this.activeDate, Ext.Date.MONTH, -1));
100563     },
100564
100565     /**
100566      * Show the next month.
100567      * @return {Ext.picker.Date} this
100568      */
100569     showNextMonth : function(e){
100570         return this.update(Ext.Date.add(this.activeDate, Ext.Date.MONTH, 1));
100571     },
100572
100573     /**
100574      * Show the previous year.
100575      * @return {Ext.picker.Date} this
100576      */
100577     showPrevYear : function(){
100578         this.update(Ext.Date.add(this.activeDate, Ext.Date.YEAR, -1));
100579     },
100580
100581     /**
100582      * Show the next year.
100583      * @return {Ext.picker.Date} this
100584      */
100585     showNextYear : function(){
100586         this.update(Ext.Date.add(this.activeDate, Ext.Date.YEAR, 1));
100587     },
100588
100589     /**
100590      * Respond to the mouse wheel event
100591      * @private
100592      * @param {Ext.EventObject} e
100593      */
100594     handleMouseWheel : function(e){
100595         e.stopEvent();
100596         if(!this.disabled){
100597             var delta = e.getWheelDelta();
100598             if(delta > 0){
100599                 this.showPrevMonth();
100600             } else if(delta < 0){
100601                 this.showNextMonth();
100602             }
100603         }
100604     },
100605
100606     /**
100607      * Respond to a date being clicked in the picker
100608      * @private
100609      * @param {Ext.EventObject} e
100610      * @param {HTMLElement} t
100611      */
100612     handleDateClick : function(e, t){
100613         var me = this,
100614             handler = me.handler;
100615
100616         e.stopEvent();
100617         if(!me.disabled && t.dateValue && !Ext.fly(t.parentNode).hasCls(me.disabledCellCls)){
100618             me.cancelFocus = me.focusOnSelect === false;
100619             me.setValue(new Date(t.dateValue));
100620             delete me.cancelFocus;
100621             me.fireEvent('select', me, me.value);
100622             if (handler) {
100623                 handler.call(me.scope || me, me, me.value);
100624             }
100625             // event handling is turned off on hide
100626             // when we are using the picker in a field
100627             // therefore onSelect comes AFTER the select
100628             // event.
100629             me.onSelect();
100630         }
100631     },
100632
100633     /**
100634      * Perform any post-select actions
100635      * @private
100636      */
100637     onSelect: function() {
100638         if (this.hideOnSelect) {
100639              this.hide();
100640          }
100641     },
100642
100643     /**
100644      * Sets the current value to today.
100645      * @return {Ext.picker.Date} this
100646      */
100647     selectToday : function(){
100648         var me = this,
100649             btn = me.todayBtn,
100650             handler = me.handler;
100651
100652         if(btn && !btn.disabled){
100653             me.setValue(Ext.Date.clearTime(new Date()));
100654             me.fireEvent('select', me, me.value);
100655             if (handler) {
100656                 handler.call(me.scope || me, me, me.value);
100657             }
100658             me.onSelect();
100659         }
100660         return me;
100661     },
100662
100663     /**
100664      * Update the selected cell
100665      * @private
100666      * @param {Date} date The new date
100667      * @param {Date} active The active date
100668      */
100669     selectedUpdate: function(date, active){
100670         var me = this,
100671             t = date.getTime(),
100672             cells = me.cells,
100673             cls = me.selectedCls;
100674
100675         cells.removeCls(cls);
100676         cells.each(function(c){
100677             if (c.dom.firstChild.dateValue == t) {
100678                 me.el.dom.setAttribute('aria-activedescendent', c.dom.id);
100679                 c.addCls(cls);
100680                 if(me.isVisible() && !me.cancelFocus){
100681                     Ext.fly(c.dom.firstChild).focus(50);
100682                 }
100683                 return false;
100684             }
100685         }, this);
100686     },
100687
100688     /**
100689      * Update the contents of the picker for a new month
100690      * @private
100691      * @param {Date} date The new date
100692      * @param {Date} active The active date
100693      */
100694     fullUpdate: function(date, active){
100695         var me = this,
100696             cells = me.cells.elements,
100697             textNodes = me.textNodes,
100698             disabledCls = me.disabledCellCls,
100699             eDate = Ext.Date,
100700             i = 0,
100701             extraDays = 0,
100702             visible = me.isVisible(),
100703             sel = +eDate.clearTime(date, true),
100704             today = +eDate.clearTime(new Date()),
100705             min = me.minDate ? eDate.clearTime(me.minDate, true) : Number.NEGATIVE_INFINITY,
100706             max = me.maxDate ? eDate.clearTime(me.maxDate, true) : Number.POSITIVE_INFINITY,
100707             ddMatch = me.disabledDatesRE,
100708             ddText = me.disabledDatesText,
100709             ddays = me.disabledDays ? me.disabledDays.join('') : false,
100710             ddaysText = me.disabledDaysText,
100711             format = me.format,
100712             days = eDate.getDaysInMonth(date),
100713             firstOfMonth = eDate.getFirstDateOfMonth(date),
100714             startingPos = firstOfMonth.getDay() - me.startDay,
100715             previousMonth = eDate.add(date, eDate.MONTH, -1),
100716             longDayFormat = me.longDayFormat,
100717             prevStart,
100718             current,
100719             disableToday,
100720             tempDate,
100721             setCellClass,
100722             html,
100723             cls,
100724             formatValue,
100725             value;
100726
100727         if (startingPos < 0) {
100728             startingPos += 7;
100729         }
100730
100731         days += startingPos;
100732         prevStart = eDate.getDaysInMonth(previousMonth) - startingPos;
100733         current = new Date(previousMonth.getFullYear(), previousMonth.getMonth(), prevStart, me.initHour);
100734
100735         if (me.showToday) {
100736             tempDate = eDate.clearTime(new Date());
100737             disableToday = (tempDate < min || tempDate > max ||
100738                 (ddMatch && format && ddMatch.test(eDate.dateFormat(tempDate, format))) ||
100739                 (ddays && ddays.indexOf(tempDate.getDay()) != -1));
100740
100741             if (!me.disabled) {
100742                 me.todayBtn.setDisabled(disableToday);
100743                 me.todayKeyListener.setDisabled(disableToday);
100744             }
100745         }
100746
100747         setCellClass = function(cell){
100748             value = +eDate.clearTime(current, true);
100749             cell.title = eDate.format(current, longDayFormat);
100750             // store dateValue number as an expando
100751             cell.firstChild.dateValue = value;
100752             if(value == today){
100753                 cell.className += ' ' + me.todayCls;
100754                 cell.title = me.todayText;
100755             }
100756             if(value == sel){
100757                 cell.className += ' ' + me.selectedCls;
100758                 me.el.dom.setAttribute('aria-activedescendant', cell.id);
100759                 if (visible && me.floating) {
100760                     Ext.fly(cell.firstChild).focus(50);
100761                 }
100762             }
100763             // disabling
100764             if(value < min) {
100765                 cell.className = disabledCls;
100766                 cell.title = me.minText;
100767                 return;
100768             }
100769             if(value > max) {
100770                 cell.className = disabledCls;
100771                 cell.title = me.maxText;
100772                 return;
100773             }
100774             if(ddays){
100775                 if(ddays.indexOf(current.getDay()) != -1){
100776                     cell.title = ddaysText;
100777                     cell.className = disabledCls;
100778                 }
100779             }
100780             if(ddMatch && format){
100781                 formatValue = eDate.dateFormat(current, format);
100782                 if(ddMatch.test(formatValue)){
100783                     cell.title = ddText.replace('%0', formatValue);
100784                     cell.className = disabledCls;
100785                 }
100786             }
100787         };
100788
100789         for(; i < me.numDays; ++i) {
100790             if (i < startingPos) {
100791                 html = (++prevStart);
100792                 cls = me.prevCls;
100793             } else if (i >= days) {
100794                 html = (++extraDays);
100795                 cls = me.nextCls;
100796             } else {
100797                 html = i - startingPos + 1;
100798                 cls = me.activeCls;
100799             }
100800             textNodes[i].innerHTML = html;
100801             cells[i].className = cls;
100802             current.setDate(current.getDate() + 1);
100803             setCellClass(cells[i]);
100804         }
100805
100806         me.monthBtn.setText(me.monthNames[date.getMonth()] + ' ' + date.getFullYear());
100807     },
100808
100809     /**
100810      * Update the contents of the picker
100811      * @private
100812      * @param {Date} date The new date
100813      * @param {Boolean} forceRefresh True to force a full refresh
100814      */
100815     update : function(date, forceRefresh){
100816         var me = this,
100817             active = me.activeDate;
100818
100819         if (me.rendered) {
100820             me.activeDate = date;
100821             if(!forceRefresh && active && me.el && active.getMonth() == date.getMonth() && active.getFullYear() == date.getFullYear()){
100822                 me.selectedUpdate(date, active);
100823             } else {
100824                 me.fullUpdate(date, active);
100825             }
100826         }
100827         return me;
100828     },
100829
100830     // private, inherit docs
100831     beforeDestroy : function() {
100832         var me = this;
100833
100834         if (me.rendered) {
100835             Ext.destroy(
100836                 me.todayKeyListener,
100837                 me.keyNav,
100838                 me.monthPicker,
100839                 me.monthBtn,
100840                 me.nextRepeater,
100841                 me.prevRepeater,
100842                 me.todayBtn
100843             );
100844             delete me.textNodes;
100845             delete me.cells.elements;
100846         }
100847     },
100848
100849     // private, inherit docs
100850     onShow: function() {
100851         this.callParent(arguments);
100852         if (this.focusOnShow) {
100853             this.focus();
100854         }
100855     }
100856 },
100857
100858 // After dependencies have loaded:
100859 function() {
100860     var proto = this.prototype;
100861
100862     proto.monthNames = Ext.Date.monthNames;
100863
100864     proto.dayNames = Ext.Date.dayNames;
100865
100866     proto.format = Ext.Date.defaultFormat;
100867 });
100868
100869 /**
100870  * @class Ext.form.field.Date
100871  * @extends Ext.form.field.Picker
100872
100873 Provides a date input field with a {@link Ext.picker.Date date picker} dropdown and automatic date
100874 validation.
100875
100876 This field recognizes and uses the JavaScript Date object as its main {@link #value} type. In addition,
100877 it recognizes string values which are parsed according to the {@link #format} and/or {@link #altFormats}
100878 configs. These may be reconfigured to use date formats appropriate for the user's locale.
100879
100880 The field may be limited to a certain range of dates by using the {@link #minValue}, {@link #maxValue},
100881 {@link #disabledDays}, and {@link #disabledDates} config parameters. These configurations will be used both
100882 in the field's validation, and in the date picker dropdown by preventing invalid dates from being selected.
100883 {@img Ext.form.Date/Ext.form.Date.png Ext.form.Date component}
100884 #Example usage:#
100885
100886     Ext.create('Ext.form.Panel', {
100887         width: 300,
100888         bodyPadding: 10,
100889         title: 'Dates',
100890         items: [{
100891             xtype: 'datefield',
100892             anchor: '100%',
100893             fieldLabel: 'From',
100894             name: 'from_date',
100895             maxValue: new Date()  // limited to the current date or prior
100896         }, {
100897             xtype: 'datefield',
100898             anchor: '100%',
100899             fieldLabel: 'To',
100900             name: 'to_date',
100901             value: new Date()  // defaults to today
100902         }],
100903             renderTo: Ext.getBody()
100904     });
100905
100906 #Date Formats Examples#
100907
100908 This example shows a couple of different date format parsing scenarios. Both use custom date format
100909 configurations; the first one matches the configured `format` while the second matches the `altFormats`.
100910
100911     Ext.create('Ext.form.Panel', {
100912         renderTo: Ext.getBody(),
100913         width: 300,
100914         bodyPadding: 10,
100915         title: 'Dates',
100916         items: [{
100917             xtype: 'datefield',
100918             anchor: '100%',
100919             fieldLabel: 'Date',
100920             name: 'date',
100921             // The value matches the format; will be parsed and displayed using that format.
100922             format: 'm d Y',
100923             value: '2 4 1978'
100924         }, {
100925             xtype: 'datefield',
100926             anchor: '100%',
100927             fieldLabel: 'Date',
100928             name: 'date',
100929             // The value does not match the format, but does match an altFormat; will be parsed
100930             // using the altFormat and displayed using the format.
100931             format: 'm d Y',
100932             altFormats: 'm,d,Y|m.d.Y',
100933             value: '2.4.1978'
100934         }]
100935     });
100936
100937  * @constructor
100938  * Create a new Date field
100939  * @param {Object} config
100940  * 
100941  * @xtype datefield
100942  * @markdown
100943  * @docauthor Jason Johnston <jason@sencha.com>
100944  */
100945 Ext.define('Ext.form.field.Date', {
100946     extend:'Ext.form.field.Picker',
100947     alias: 'widget.datefield',
100948     requires: ['Ext.picker.Date'],
100949     alternateClassName: ['Ext.form.DateField', 'Ext.form.Date'],
100950
100951     /**
100952      * @cfg {String} format
100953      * The default date format string which can be overriden for localization support.  The format must be
100954      * valid according to {@link Ext.Date#parse} (defaults to <tt>'m/d/Y'</tt>).
100955      */
100956     format : "m/d/Y",
100957     /**
100958      * @cfg {String} altFormats
100959      * Multiple date formats separated by "<tt>|</tt>" to try when parsing a user input value and it
100960      * does not match the defined format (defaults to
100961      * <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>).
100962      */
100963     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",
100964     /**
100965      * @cfg {String} disabledDaysText
100966      * The tooltip to display when the date falls on a disabled day (defaults to <tt>'Disabled'</tt>)
100967      */
100968     disabledDaysText : "Disabled",
100969     /**
100970      * @cfg {String} disabledDatesText
100971      * The tooltip text to display when the date falls on a disabled date (defaults to <tt>'Disabled'</tt>)
100972      */
100973     disabledDatesText : "Disabled",
100974     /**
100975      * @cfg {String} minText
100976      * The error text to display when the date in the cell is before <tt>{@link #minValue}</tt> (defaults to
100977      * <tt>'The date in this field must be after {minValue}'</tt>).
100978      */
100979     minText : "The date in this field must be equal to or after {0}",
100980     /**
100981      * @cfg {String} maxText
100982      * The error text to display when the date in the cell is after <tt>{@link #maxValue}</tt> (defaults to
100983      * <tt>'The date in this field must be before {maxValue}'</tt>).
100984      */
100985     maxText : "The date in this field must be equal to or before {0}",
100986     /**
100987      * @cfg {String} invalidText
100988      * The error text to display when the date in the field is invalid (defaults to
100989      * <tt>'{value} is not a valid date - it must be in the format {format}'</tt>).
100990      */
100991     invalidText : "{0} is not a valid date - it must be in the format {1}",
100992     /**
100993      * @cfg {String} triggerCls
100994      * An additional CSS class used to style the trigger button.  The trigger will always get the
100995      * class <tt>'x-form-trigger'</tt> and <tt>triggerCls</tt> will be <b>appended</b> if specified
100996      * (defaults to <tt>'x-form-date-trigger'</tt> which displays a calendar icon).
100997      */
100998     triggerCls : Ext.baseCSSPrefix + 'form-date-trigger',
100999     /**
101000      * @cfg {Boolean} showToday
101001      * <tt>false</tt> to hide the footer area of the Date picker containing the Today button and disable
101002      * the keyboard handler for spacebar that selects the current date (defaults to <tt>true</tt>).
101003      */
101004     showToday : true,
101005     /**
101006      * @cfg {Date/String} minValue
101007      * The minimum allowed date. Can be either a Javascript date object or a string date in a
101008      * valid format (defaults to undefined).
101009      */
101010     /**
101011      * @cfg {Date/String} maxValue
101012      * The maximum allowed date. Can be either a Javascript date object or a string date in a
101013      * valid format (defaults to undefined).
101014      */
101015     /**
101016      * @cfg {Array} disabledDays
101017      * An array of days to disable, 0 based (defaults to undefined). Some examples:<pre><code>
101018 // disable Sunday and Saturday:
101019 disabledDays:  [0, 6]
101020 // disable weekdays:
101021 disabledDays: [1,2,3,4,5]
101022      * </code></pre>
101023      */
101024     /**
101025      * @cfg {Array} disabledDates
101026      * An array of "dates" to disable, as strings. These strings will be used to build a dynamic regular
101027      * expression so they are very powerful. Some examples:<pre><code>
101028 // disable these exact dates:
101029 disabledDates: ["03/08/2003", "09/16/2003"]
101030 // disable these days for every year:
101031 disabledDates: ["03/08", "09/16"]
101032 // only match the beginning (useful if you are using short years):
101033 disabledDates: ["^03/08"]
101034 // disable every day in March 2006:
101035 disabledDates: ["03/../2006"]
101036 // disable every day in every March:
101037 disabledDates: ["^03"]
101038      * </code></pre>
101039      * Note that the format of the dates included in the array should exactly match the {@link #format} config.
101040      * In order to support regular expressions, if you are using a {@link #format date format} that has "." in
101041      * it, you will have to escape the dot when restricting dates. For example: <tt>["03\\.08\\.03"]</tt>.
101042      */
101043     
101044     /**
101045      * @cfg {String} submitFormat The date format string which will be submitted to the server.  
101046      * The format must be valid according to {@link Ext.Date#parse} (defaults to <tt>{@link #format}</tt>).
101047      */
101048
101049     // in the absence of a time value, a default value of 12 noon will be used
101050     // (note: 12 noon was chosen because it steers well clear of all DST timezone changes)
101051     initTime: '12', // 24 hour format
101052
101053     initTimeFormat: 'H',
101054
101055     matchFieldWidth: false,
101056     /**
101057      * @cfg {Number} startDay
101058      * Day index at which the week should begin, 0-based (defaults to 0, which is Sunday)
101059      */
101060     startDay: 0,
101061     
101062     initComponent : function(){
101063         var me = this,
101064             isString = Ext.isString,
101065             min, max;
101066
101067         min = me.minValue;
101068         max = me.maxValue;
101069         if(isString(min)){
101070             me.minValue = me.parseDate(min);
101071         }
101072         if(isString(max)){
101073             me.maxValue = me.parseDate(max);
101074         }
101075         me.disabledDatesRE = null;
101076         me.initDisabledDays();
101077
101078         me.callParent();
101079     },
101080
101081     initValue: function() {
101082         var me = this,
101083             value = me.value;
101084
101085         // If a String value was supplied, try to convert it to a proper Date
101086         if (Ext.isString(value)) {
101087             me.value = me.rawToValue(value);
101088         }
101089
101090         me.callParent();
101091     },
101092
101093     // private
101094     initDisabledDays : function(){
101095         if(this.disabledDates){
101096             var dd = this.disabledDates,
101097                 len = dd.length - 1,
101098                 re = "(?:";
101099
101100             Ext.each(dd, function(d, i){
101101                 re += Ext.isDate(d) ? '^' + Ext.String.escapeRegex(d.dateFormat(this.format)) + '$' : dd[i];
101102                 if (i !== len) {
101103                     re += '|';
101104                 }
101105             }, this);
101106             this.disabledDatesRE = new RegExp(re + ')');
101107         }
101108     },
101109
101110     /**
101111      * Replaces any existing disabled dates with new values and refreshes the Date picker.
101112      * @param {Array} disabledDates An array of date strings (see the <tt>{@link #disabledDates}</tt> config
101113      * for details on supported values) used to disable a pattern of dates.
101114      */
101115     setDisabledDates : function(dd){
101116         var me = this,
101117             picker = me.picker;
101118             
101119         me.disabledDates = dd;
101120         me.initDisabledDays();
101121         if (picker) {
101122             picker.setDisabledDates(me.disabledDatesRE);
101123         }
101124     },
101125
101126     /**
101127      * Replaces any existing disabled days (by index, 0-6) with new values and refreshes the Date picker.
101128      * @param {Array} disabledDays An array of disabled day indexes. See the <tt>{@link #disabledDays}</tt>
101129      * config for details on supported values.
101130      */
101131     setDisabledDays : function(dd){
101132         var picker = this.picker;
101133             
101134         this.disabledDays = dd;
101135         if (picker) {
101136             picker.setDisabledDays(dd);
101137         }
101138     },
101139
101140     /**
101141      * Replaces any existing <tt>{@link #minValue}</tt> with the new value and refreshes the Date picker.
101142      * @param {Date} value The minimum date that can be selected
101143      */
101144     setMinValue : function(dt){
101145         var me = this,
101146             picker = me.picker,
101147             minValue = (Ext.isString(dt) ? me.parseDate(dt) : dt);
101148             
101149         me.minValue = minValue;
101150         if (picker) {
101151             picker.minText = Ext.String.format(me.minText, me.formatDate(me.minValue));
101152             picker.setMinDate(minValue);
101153         }
101154     },
101155
101156     /**
101157      * Replaces any existing <tt>{@link #maxValue}</tt> with the new value and refreshes the Date picker.
101158      * @param {Date} value The maximum date that can be selected
101159      */
101160     setMaxValue : function(dt){
101161         var me = this,
101162             picker = me.picker,
101163             maxValue = (Ext.isString(dt) ? me.parseDate(dt) : dt);
101164             
101165         me.maxValue = maxValue;
101166         if (picker) {
101167             picker.maxText = Ext.String.format(me.maxText, me.formatDate(me.maxValue));
101168             picker.setMaxDate(maxValue);
101169         }
101170     },
101171
101172     /**
101173      * Runs all of Date's validations and returns an array of any errors. Note that this first
101174      * runs Text's validations, so the returned array is an amalgamation of all field errors.
101175      * The additional validation checks are testing that the date format is valid, that the chosen
101176      * date is within the min and max date constraints set, that the date chosen is not in the disabledDates
101177      * regex and that the day chosed is not one of the disabledDays.
101178      * @param {Mixed} value The value to get errors for (defaults to the current field value)
101179      * @return {Array} All validation errors for this field
101180      */
101181     getErrors: function(value) {
101182         var me = this,
101183             format = Ext.String.format,
101184             clearTime = Ext.Date.clearTime,
101185             errors = me.callParent(arguments),
101186             disabledDays = me.disabledDays,
101187             disabledDatesRE = me.disabledDatesRE,
101188             minValue = me.minValue,
101189             maxValue = me.maxValue,
101190             len = disabledDays ? disabledDays.length : 0,
101191             i = 0,
101192             svalue,
101193             fvalue,
101194             day,
101195             time;
101196
101197         value = me.formatDate(value || me.processRawValue(me.getRawValue()));
101198
101199         if (value === null || value.length < 1) { // if it's blank and textfield didn't flag it then it's valid
101200              return errors;
101201         }
101202
101203         svalue = value;
101204         value = me.parseDate(value);
101205         if (!value) {
101206             errors.push(format(me.invalidText, svalue, me.format));
101207             return errors;
101208         }
101209
101210         time = value.getTime();
101211         if (minValue && time < clearTime(minValue).getTime()) {
101212             errors.push(format(me.minText, me.formatDate(minValue)));
101213         }
101214
101215         if (maxValue && time > clearTime(maxValue).getTime()) {
101216             errors.push(format(me.maxText, me.formatDate(maxValue)));
101217         }
101218
101219         if (disabledDays) {
101220             day = value.getDay();
101221
101222             for(; i < len; i++) {
101223                 if (day === disabledDays[i]) {
101224                     errors.push(me.disabledDaysText);
101225                     break;
101226                 }
101227             }
101228         }
101229
101230         fvalue = me.formatDate(value);
101231         if (disabledDatesRE && disabledDatesRE.test(fvalue)) {
101232             errors.push(format(me.disabledDatesText, fvalue));
101233         }
101234
101235         return errors;
101236     },
101237
101238     rawToValue: function(rawValue) {
101239         return this.parseDate(rawValue) || rawValue || null;
101240     },
101241
101242     valueToRaw: function(value) {
101243         return this.formatDate(this.parseDate(value));
101244     },
101245
101246     /**
101247      * Sets the value of the date field.  You can pass a date object or any string that can be
101248      * parsed into a valid date, using <tt>{@link #format}</tt> as the date format, according
101249      * to the same rules as {@link Ext.Date#parse} (the default format used is <tt>"m/d/Y"</tt>).
101250      * <br />Usage:
101251      * <pre><code>
101252 //All of these calls set the same date value (May 4, 2006)
101253
101254 //Pass a date object:
101255 var dt = new Date('5/4/2006');
101256 dateField.setValue(dt);
101257
101258 //Pass a date string (default format):
101259 dateField.setValue('05/04/2006');
101260
101261 //Pass a date string (custom format):
101262 dateField.format = 'Y-m-d';
101263 dateField.setValue('2006-05-04');
101264 </code></pre>
101265      * @param {String/Date} date The date or valid date string
101266      * @return {Ext.form.field.Date} this
101267      * @method setValue
101268      */
101269
101270     /**
101271      * Attempts to parse a given string value using a given {@link Ext.Date#parse date format}.
101272      * @param {String} value The value to attempt to parse
101273      * @param {String} format A valid date format (see {@link Ext.Date#parse})
101274      * @return {Date} The parsed Date object, or null if the value could not be successfully parsed.
101275      */
101276     safeParse : function(value, format) {
101277         var me = this,
101278             utilDate = Ext.Date,
101279             parsedDate,
101280             result = null;
101281             
101282         if (utilDate.formatContainsHourInfo(format)) {
101283             // if parse format contains hour information, no DST adjustment is necessary
101284             result = utilDate.parse(value, format);
101285         } else {
101286             // set time to 12 noon, then clear the time
101287             parsedDate = utilDate.parse(value + ' ' + me.initTime, format + ' ' + me.initTimeFormat);
101288             if (parsedDate) {
101289                 result = utilDate.clearTime(parsedDate);
101290             }
101291         }
101292         return result;
101293     },
101294     
101295     // @private
101296     getSubmitValue: function() {
101297         var me = this,
101298             format = me.submitFormat || me.format,
101299             value = me.getValue();
101300             
101301         return value ? Ext.Date.format(value, format) : null;
101302     },
101303
101304     /**
101305      * @private
101306      */
101307     parseDate : function(value) {
101308         if(!value || Ext.isDate(value)){
101309             return value;
101310         }
101311
101312         var me = this,
101313             val = me.safeParse(value, me.format),
101314             altFormats = me.altFormats,
101315             altFormatsArray = me.altFormatsArray,
101316             i = 0,
101317             len;
101318
101319         if (!val && altFormats) {
101320             altFormatsArray = altFormatsArray || altFormats.split('|');
101321             len = altFormatsArray.length;
101322             for (; i < len && !val; ++i) {
101323                 val = me.safeParse(value, altFormatsArray[i]);
101324             }
101325         }
101326         return val;
101327     },
101328
101329     // private
101330     formatDate : function(date){
101331         return Ext.isDate(date) ? Ext.Date.dateFormat(date, this.format) : date;
101332     },
101333
101334     createPicker: function() {
101335         var me = this,
101336             format = Ext.String.format;
101337
101338         return Ext.create('Ext.picker.Date', {
101339             ownerCt: this.ownerCt,
101340             renderTo: document.body,
101341             floating: true,
101342             hidden: true,
101343             focusOnShow: true,
101344             minDate: me.minValue,
101345             maxDate: me.maxValue,
101346             disabledDatesRE: me.disabledDatesRE,
101347             disabledDatesText: me.disabledDatesText,
101348             disabledDays: me.disabledDays,
101349             disabledDaysText: me.disabledDaysText,
101350             format: me.format,
101351             showToday: me.showToday,
101352             startDay: me.startDay,
101353             minText: format(me.minText, me.formatDate(me.minValue)),
101354             maxText: format(me.maxText, me.formatDate(me.maxValue)),
101355             listeners: {
101356                 scope: me,
101357                 select: me.onSelect
101358             },
101359             keyNavConfig: {
101360                 esc: function() {
101361                     me.collapse();
101362                 }
101363             }
101364         });
101365     },
101366
101367     onSelect: function(m, d) {
101368         this.setValue(d);
101369         this.fireEvent('select', this, d);
101370         this.collapse();
101371     },
101372
101373     /**
101374      * @private
101375      * Sets the Date picker's value to match the current field value when expanding.
101376      */
101377     onExpand: function() {
101378         var me = this,
101379             value = me.getValue();
101380         me.picker.setValue(value instanceof Date ? value : new Date());
101381     },
101382
101383     /**
101384      * @private
101385      * Focuses the field when collapsing the Date picker.
101386      */
101387     onCollapse: function() {
101388         this.focus(false, 60);
101389     },
101390
101391     // private
101392     beforeBlur : function(){
101393         var v = this.parseDate(this.getRawValue());
101394         if(v){
101395             this.setValue(v);
101396         }
101397     }
101398
101399     /**
101400      * @cfg {Boolean} grow @hide
101401      */
101402     /**
101403      * @cfg {Number} growMin @hide
101404      */
101405     /**
101406      * @cfg {Number} growMax @hide
101407      */
101408     /**
101409      * @hide
101410      * @method autoSize
101411      */
101412 });
101413
101414 /**
101415  * @class Ext.form.field.Display
101416  * @extends Ext.form.field.Base
101417  * <p>A display-only text field which is not validated and not submitted. This is useful for when you want
101418  * to display a value from a form's {@link Ext.form.Basic#load loaded data} but do not want to allow the
101419  * user to edit or submit that value. The value can be optionally {@link #htmlEncode HTML encoded} if it contains
101420  * HTML markup that you do not want to be rendered.</p>
101421  * <p>If you have more complex content, or need to include components within the displayed content, also
101422  * consider using a {@link Ext.form.FieldContainer} instead.</p>
101423  * {@img Ext.form.Display/Ext.form.Display.png Ext.form.Display component}
101424  * <p>Example:</p>
101425  * <pre><code>
101426     Ext.create('Ext.form.Panel', {
101427         width: 175,
101428         height: 120,
101429         bodyPadding: 10,
101430         title: 'Final Score',
101431         items: [{
101432             xtype: 'displayfield',
101433             fieldLabel: 'Home',
101434             name: 'home_score',
101435             value: '10'
101436         }, {
101437             xtype: 'displayfield',
101438             fieldLabel: 'Visitor',
101439             name: 'visitor_score',
101440             value: '11'
101441         }],
101442         buttons: [{
101443             text: 'Update',
101444         }],
101445         renderTo: Ext.getBody()
101446     });
101447 </code></pre>
101448
101449  * @constructor
101450  * Creates a new DisplayField.
101451  * @param {Object} config Configuration options
101452  *
101453  * @xtype displayfield
101454  */
101455 Ext.define('Ext.form.field.Display', {
101456     extend:'Ext.form.field.Base',
101457     alias: 'widget.displayfield',
101458     requires: ['Ext.util.Format', 'Ext.XTemplate'],
101459     alternateClassName: ['Ext.form.DisplayField', 'Ext.form.Display'],
101460     fieldSubTpl: [
101461         '<div id="{id}" class="{fieldCls}"></div>',
101462         {
101463             compiled: true,
101464             disableFormats: true
101465         }
101466     ],
101467
101468     /**
101469      * @cfg {String} fieldCls The default CSS class for the field (defaults to <tt>"x-form-display-field"</tt>)
101470      */
101471     fieldCls: Ext.baseCSSPrefix + 'form-display-field',
101472
101473     /**
101474      * @cfg {Boolean} htmlEncode <tt>false</tt> to skip HTML-encoding the text when rendering it (defaults to
101475      * <tt>false</tt>). This might be useful if you want to include tags in the field's innerHTML rather than
101476      * rendering them as string literals per the default logic.
101477      */
101478     htmlEncode: false,
101479
101480     validateOnChange: false,
101481
101482     initEvents: Ext.emptyFn,
101483
101484     submitValue: false,
101485
101486     isValid: function() {
101487         return true;
101488     },
101489
101490     validate: function() {
101491         return true;
101492     },
101493
101494     getRawValue: function() {
101495         return this.rawValue;
101496     },
101497
101498     setRawValue: function(value) {
101499         var me = this;
101500         value = Ext.value(value, '');
101501         me.rawValue = value;
101502         if (me.rendered) {
101503             me.inputEl.dom.innerHTML = me.htmlEncode ? Ext.util.Format.htmlEncode(value) : value;
101504         }
101505         return value;
101506     },
101507
101508     // private
101509     getContentTarget: function() {
101510         return this.inputEl;
101511     }
101512
101513     /**
101514      * @cfg {String} inputType
101515      * @hide
101516      */
101517     /**
101518      * @cfg {Boolean} disabled
101519      * @hide
101520      */
101521     /**
101522      * @cfg {Boolean} readOnly
101523      * @hide
101524      */
101525     /**
101526      * @cfg {Boolean} validateOnChange
101527      * @hide
101528      */
101529     /**
101530      * @cfg {Number} checkChangeEvents
101531      * @hide
101532      */
101533     /**
101534      * @cfg {Number} checkChangeBuffer
101535      * @hide
101536      */
101537 });
101538
101539 /**
101540  * @class Ext.form.field.File
101541  * @extends Ext.form.field.Text
101542
101543 A file upload field which has custom styling and allows control over the button text and other
101544 features of {@link Ext.form.field.Text text fields} like {@link Ext.form.field.Text#emptyText empty text}.
101545 It uses a hidden file input element behind the scenes to allow user selection of a file and to
101546 perform the actual upload during {@link Ext.form.Basic#submit form submit}.
101547
101548 Because there is no secure cross-browser way to programmatically set the value of a file input,
101549 the standard Field `setValue` method is not implemented. The `{@link #getValue}` method will return
101550 a value that is browser-dependent; some have just the file name, some have a full path, some use
101551 a fake path.
101552 {@img Ext.form.File/Ext.form.File.png Ext.form.File component}
101553 #Example Usage:#
101554
101555     Ext.create('Ext.form.Panel', {
101556         title: 'Upload a Photo',
101557         width: 400,
101558         bodyPadding: 10,
101559         frame: true,
101560         renderTo: Ext.getBody(),    
101561         items: [{
101562             xtype: 'filefield',
101563             name: 'photo',
101564             fieldLabel: 'Photo',
101565             labelWidth: 50,
101566             msgTarget: 'side',
101567             allowBlank: false,
101568             anchor: '100%',
101569             buttonText: 'Select Photo...'
101570         }],
101571     
101572         buttons: [{
101573             text: 'Upload',
101574             handler: function() {
101575                 var form = this.up('form').getForm();
101576                 if(form.isValid()){
101577                     form.submit({
101578                         url: 'photo-upload.php',
101579                         waitMsg: 'Uploading your photo...',
101580                         success: function(fp, o) {
101581                             Ext.Msg.alert('Success', 'Your photo "' + o.result.file + '" has been uploaded.');
101582                         }
101583                     });
101584                 }
101585             }
101586         }]
101587     });
101588
101589  * @constructor
101590  * Create a new File field
101591  * @param {Object} config Configuration options
101592  * @xtype filefield
101593  * @markdown
101594  * @docauthor Jason Johnston <jason@sencha.com>
101595  */
101596 Ext.define("Ext.form.field.File", {
101597     extend: 'Ext.form.field.Text',
101598     alias: ['widget.filefield', 'widget.fileuploadfield'],
101599     alternateClassName: ['Ext.form.FileUploadField', 'Ext.ux.form.FileUploadField', 'Ext.form.File'],
101600     uses: ['Ext.button.Button', 'Ext.layout.component.field.File'],
101601
101602     /**
101603      * @cfg {String} buttonText The button text to display on the upload button (defaults to
101604      * 'Browse...').  Note that if you supply a value for {@link #buttonConfig}, the buttonConfig.text
101605      * value will be used instead if available.
101606      */
101607     buttonText: 'Browse...',
101608
101609     /**
101610      * @cfg {Boolean} buttonOnly True to display the file upload field as a button with no visible
101611      * text field (defaults to false).  If true, all inherited Text members will still be available.
101612      */
101613     buttonOnly: false,
101614
101615     /**
101616      * @cfg {Number} buttonMargin The number of pixels of space reserved between the button and the text field
101617      * (defaults to 3).  Note that this only applies if {@link #buttonOnly} = false.
101618      */
101619     buttonMargin: 3,
101620
101621     /**
101622      * @cfg {Object} buttonConfig A standard {@link Ext.button.Button} config object.
101623      */
101624
101625     /**
101626      * @event change
101627      * Fires when the underlying file input field's value has changed from the user
101628      * selecting a new file from the system file selection dialog.
101629      * @param {Ext.ux.form.FileUploadField} this
101630      * @param {String} value The file value returned by the underlying file input field
101631      */
101632
101633     /**
101634      * @property fileInputEl
101635      * @type {Ext.core.Element}
101636      * A reference to the invisible file input element created for this upload field. Only
101637      * populated after this component is rendered.
101638      */
101639
101640     /**
101641      * @property button
101642      * @type {Ext.button.Button}
101643      * A reference to the trigger Button component created for this upload field. Only
101644      * populated after this component is rendered.
101645      */
101646
101647     /**
101648      * @cfg {String} fieldBodyCls
101649      * An extra CSS class to be applied to the body content element in addition to {@link #fieldBodyCls}.
101650      * Defaults to 'x-form-file-wrap' for file upload field.
101651      */
101652     fieldBodyCls: Ext.baseCSSPrefix + 'form-file-wrap',
101653
101654
101655     // private
101656     readOnly: true,
101657     componentLayout: 'filefield',
101658
101659     // private
101660     onRender: function() {
101661         var me = this,
101662             inputEl;
101663
101664         me.callParent(arguments);
101665
101666         me.createButton();
101667         me.createFileInput();
101668         
101669         // we don't create the file/button til after onRender, the initial disable() is
101670         // called in the onRender of the component.
101671         if (me.disabled) {
101672             me.disableItems();
101673         }
101674
101675         inputEl = me.inputEl;
101676         inputEl.dom.removeAttribute('name'); //name goes on the fileInput, not the text input
101677         if (me.buttonOnly) {
101678             inputEl.setDisplayed(false);
101679         }
101680     },
101681
101682     /**
101683      * @private
101684      * Creates the custom trigger Button component. The fileInput will be inserted into this.
101685      */
101686     createButton: function() {
101687         var me = this;
101688         me.button = Ext.widget('button', Ext.apply({
101689             renderTo: me.bodyEl,
101690             text: me.buttonText,
101691             cls: Ext.baseCSSPrefix + 'form-file-btn',
101692             preventDefault: false,
101693             ownerCt: me,
101694             style: me.buttonOnly ? '' : 'margin-left:' + me.buttonMargin + 'px'
101695         }, me.buttonConfig));
101696     },
101697
101698     /**
101699      * @private
101700      * Creates the file input element. It is inserted into the trigger button component, made
101701      * invisible, and floated on top of the button's other content so that it will receive the
101702      * button's clicks.
101703      */
101704     createFileInput : function() {
101705         var me = this;
101706         me.fileInputEl = me.button.el.createChild({
101707             name: me.getName(),
101708             cls: Ext.baseCSSPrefix + 'form-file-input',
101709             tag: 'input',
101710             type: 'file',
101711             size: 1
101712         }).on('change', me.onFileChange, me);
101713     },
101714
101715     /**
101716      * @private Event handler fired when the user selects a file.
101717      */
101718     onFileChange: function() {
101719         this.lastValue = null; // force change event to get fired even if the user selects a file with the same name
101720         Ext.form.field.File.superclass.setValue.call(this, this.fileInputEl.dom.value);
101721     },
101722
101723     /**
101724      * Overridden to do nothing
101725      * @hide
101726      */
101727     setValue: Ext.emptyFn,
101728
101729     reset : function(){
101730         this.fileInputEl.remove();
101731         this.createFileInput();
101732         this.callParent();
101733     },
101734
101735     onDisable: function(){
101736         this.callParent();
101737         this.disableItems();
101738     },
101739     
101740     disableItems: function(){
101741         var file = this.fileInputEl,
101742             button = this.button;
101743              
101744         if (file) {
101745             file.dom.disabled = true;
101746         }
101747         if (button) {
101748             button.disable();
101749         }    
101750     },
101751
101752     onEnable: function(){
101753         var me = this;
101754         me.callParent();
101755         me.fileInputEl.dom.disabled = false;
101756         me.button.enable();
101757     },
101758
101759     isFileUpload: function() {
101760         return true;
101761     },
101762
101763     extractFileInput: function() {
101764         var fileInput = this.fileInputEl.dom;
101765         this.reset();
101766         return fileInput;
101767     },
101768
101769     onDestroy: function(){
101770         Ext.destroyMembers(this, 'fileInputEl', 'button');
101771         this.callParent();
101772     }
101773
101774
101775 });
101776
101777 /**
101778  * @class Ext.form.field.Hidden
101779  * @extends Ext.form.field.Base
101780  * <p>A basic hidden field for storing hidden values in forms that need to be passed in the form submit.</p>
101781  * <p>This creates an actual input element with type="submit" in the DOM. While its label is
101782  * {@link #hideLabel not rendered} by default, it is still a real component and may be sized according to
101783  * its owner container's layout.</p>
101784  * <p>Because of this, in most cases it is more convenient and less problematic to simply
101785  * {@link Ext.form.action.Action#params pass hidden parameters} directly when
101786  * {@link Ext.form.Basic#submit submitting the form}.</p>
101787  * <p>Example:</p>
101788  * <pre><code>new Ext.form.Panel({
101789     title: 'My Form',
101790     items: [{
101791         xtype: 'textfield',
101792         fieldLabel: 'Text Field',
101793         name: 'text_field',
101794         value: 'value from text field'
101795     }, {
101796         xtype: 'hiddenfield',
101797         name: 'hidden_field_1',
101798         value: 'value from hidden field'
101799     }],
101800
101801     buttons: [{
101802         text: 'Submit',
101803         handler: function() {
101804             this.up('form').getForm().submit({
101805                 params: {
101806                     hidden_field_2: 'value from submit call'
101807                 }
101808             });
101809         }
101810     }]
101811 });</code></pre>
101812  * <p>Submitting the above form will result in three values sent to the server:
101813  * <code>text_field=value+from+text+field&hidden_field_1=value+from+hidden+field&<br>hidden_field_2=value+from+submit+call</code></p>
101814  *
101815  * @constructor
101816  * Create a new Hidden field.
101817  * @param {Object} config Configuration options
101818  * 
101819  * @xtype hiddenfield
101820  */
101821 Ext.define('Ext.form.field.Hidden', {
101822     extend:'Ext.form.field.Base',
101823     alias: ['widget.hiddenfield', 'widget.hidden'],
101824     alternateClassName: 'Ext.form.Hidden',
101825
101826     // private
101827     inputType : 'hidden',
101828     hideLabel: true,
101829     
101830     initComponent: function(){
101831         this.formItemCls += '-hidden';
101832         this.callParent();    
101833     },
101834
101835     // These are all private overrides
101836     initEvents: Ext.emptyFn,
101837     setSize : Ext.emptyFn,
101838     setWidth : Ext.emptyFn,
101839     setHeight : Ext.emptyFn,
101840     setPosition : Ext.emptyFn,
101841     setPagePosition : Ext.emptyFn,
101842     markInvalid : Ext.emptyFn,
101843     clearInvalid : Ext.emptyFn
101844 });
101845
101846 /**
101847  * @class Ext.picker.Color
101848  * @extends Ext.Component
101849  * <p>ColorPicker provides a simple color palette for choosing colors. The picker can be rendered to any container.
101850  * The available default to a standard 40-color palette; this can be customized with the {@link #colors} config.</p>
101851  * <p>Typically you will need to implement a handler function to be notified when the user chooses a color from the
101852  * picker; you can register the handler using the {@link #select} event, or by implementing the {@link #handler}
101853  * method.</p>
101854  * <p>Here's an example of typical usage:</p>
101855  * <pre><code>var cp = new Ext.picker.Color({
101856     value: '993300',  // initial selected color
101857     renderTo: 'my-div'
101858 });
101859
101860 cp.on('select', function(picker, selColor){
101861     // do something with selColor
101862 });
101863 </code></pre>
101864  * {@img Ext.picker.Color/Ext.picker.Color.png Ext.picker.Color component}
101865  *
101866  * @constructor
101867  * Create a new ColorPicker
101868  * @param {Object} config The config object
101869  * 
101870  * @xtype colorpicker
101871  */
101872 Ext.define('Ext.picker.Color', {
101873     extend: 'Ext.Component',
101874     requires: 'Ext.XTemplate',
101875     alias: 'widget.colorpicker',
101876     alternateClassName: 'Ext.ColorPalette',
101877     
101878     /**
101879      * @cfg {String} componentCls
101880      * The CSS class to apply to the containing element (defaults to 'x-color-picker')
101881      */
101882     componentCls : Ext.baseCSSPrefix + 'color-picker',
101883     
101884     /**
101885      * @cfg {String} selectedCls
101886      * The CSS class to apply to the selected element
101887      */
101888     selectedCls: Ext.baseCSSPrefix + 'color-picker-selected',
101889     
101890     /**
101891      * @cfg {String} value
101892      * The initial color to highlight (should be a valid 6-digit color hex code without the # symbol).  Note that
101893      * the hex codes are case-sensitive.
101894      */
101895     value : null,
101896     
101897     /**
101898      * @cfg {String} clickEvent
101899      * The DOM event that will cause a color to be selected. This can be any valid event name (dblclick, contextmenu).
101900      * Defaults to <tt>'click'</tt>.
101901      */
101902     clickEvent :'click',
101903
101904     /**
101905      * @cfg {Boolean} allowReselect If set to true then reselecting a color that is already selected fires the {@link #select} event
101906      */
101907     allowReselect : false,
101908
101909     /**
101910      * <p>An array of 6-digit color hex code strings (without the # symbol).  This array can contain any number
101911      * of colors, and each hex code should be unique.  The width of the picker is controlled via CSS by adjusting
101912      * the width property of the 'x-color-picker' class (or assigning a custom class), so you can balance the number
101913      * of colors with the width setting until the box is symmetrical.</p>
101914      * <p>You can override individual colors if needed:</p>
101915      * <pre><code>
101916 var cp = new Ext.picker.Color();
101917 cp.colors[0] = 'FF0000';  // change the first box to red
101918 </code></pre>
101919
101920 Or you can provide a custom array of your own for complete control:
101921 <pre><code>
101922 var cp = new Ext.picker.Color();
101923 cp.colors = ['000000', '993300', '333300'];
101924 </code></pre>
101925      * @type Array
101926      */
101927     colors : [
101928         '000000', '993300', '333300', '003300', '003366', '000080', '333399', '333333',
101929         '800000', 'FF6600', '808000', '008000', '008080', '0000FF', '666699', '808080',
101930         'FF0000', 'FF9900', '99CC00', '339966', '33CCCC', '3366FF', '800080', '969696',
101931         'FF00FF', 'FFCC00', 'FFFF00', '00FF00', '00FFFF', '00CCFF', '993366', 'C0C0C0',
101932         'FF99CC', 'FFCC99', 'FFFF99', 'CCFFCC', 'CCFFFF', '99CCFF', 'CC99FF', 'FFFFFF'
101933     ],
101934
101935     /**
101936      * @cfg {Function} handler
101937      * Optional. A function that will handle the select event of this picker.
101938      * The handler is passed the following parameters:<div class="mdetail-params"><ul>
101939      * <li><code>picker</code> : ColorPicker<div class="sub-desc">The {@link #picker Ext.picker.Color}.</div></li>
101940      * <li><code>color</code> : String<div class="sub-desc">The 6-digit color hex code (without the # symbol).</div></li>
101941      * </ul></div>
101942      */
101943     /**
101944      * @cfg {Object} scope
101945      * The scope (<tt><b>this</b></tt> reference) in which the <code>{@link #handler}</code>
101946      * function will be called.  Defaults to this ColorPicker instance.
101947      */
101948     
101949     colorRe: /(?:^|\s)color-(.{6})(?:\s|$)/,
101950     
101951     constructor: function() {
101952         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>');
101953         this.callParent(arguments);
101954     },
101955     
101956     // private
101957     initComponent : function(){
101958         var me = this;
101959         
101960         this.callParent(arguments);
101961         me.addEvents(
101962             /**
101963              * @event select
101964              * Fires when a color is selected
101965              * @param {Ext.picker.Color} this
101966              * @param {String} color The 6-digit color hex code (without the # symbol)
101967              */
101968             'select'
101969         );
101970
101971         if (me.handler) {
101972             me.on('select', me.handler, me.scope, true);
101973         }
101974     },
101975
101976
101977     // private
101978     onRender : function(container, position){
101979         var me = this,
101980             clickEvent = me.clickEvent;
101981             
101982         Ext.apply(me.renderData, {
101983             itemCls: me.itemCls,
101984             colors: me.colors    
101985         });
101986         this.callParent(arguments);
101987
101988         me.mon(me.el, clickEvent, me.handleClick, me, {delegate: 'a'});
101989         // always stop following the anchors
101990         if(clickEvent != 'click'){
101991             me.mon(me.el, 'click', Ext.emptyFn, me, {delegate: 'a', stopEvent: true});
101992         }
101993     },
101994
101995     // private
101996     afterRender : function(){
101997         var me = this,
101998             value;
101999             
102000         this.callParent(arguments);
102001         if (me.value) {
102002             value = me.value;
102003             me.value = null;
102004             me.select(value, true);
102005         }
102006     },
102007
102008     // private
102009     handleClick : function(event, target){
102010         var me = this,
102011             color;
102012             
102013         event.stopEvent();
102014         if (!me.disabled) {
102015             color = target.className.match(me.colorRe)[1];
102016             me.select(color.toUpperCase());
102017         }
102018     },
102019
102020     /**
102021      * Selects the specified color in the picker (fires the {@link #select} event)
102022      * @param {String} color A valid 6-digit color hex code (# will be stripped if included)
102023      * @param {Boolean} suppressEvent (optional) True to stop the select event from firing. Defaults to <tt>false</tt>.
102024      */
102025     select : function(color, suppressEvent){
102026         
102027         var me = this,
102028             selectedCls = me.selectedCls,
102029             value = me.value,
102030             el;
102031             
102032         color = color.replace('#', '');
102033         if (!me.rendered) {
102034             me.value = color;
102035             return;
102036         }
102037         
102038         
102039         if (color != value || me.allowReselect) {
102040             el = me.el;
102041
102042             if (me.value) {
102043                 el.down('a.color-' + value).removeCls(selectedCls);
102044             }
102045             el.down('a.color-' + color).addCls(selectedCls);
102046             me.value = color;
102047             if (suppressEvent !== true) {
102048                 me.fireEvent('select', me, color);
102049             }
102050         }
102051     },
102052     
102053     /**
102054      * Get the currently selected color value.
102055      * @return {String} value The selected value. Null if nothing is selected.
102056      */
102057     getValue: function(){
102058         return this.value || null;
102059     }
102060 });
102061
102062 /**
102063  * @private
102064  * @class Ext.layout.component.field.HtmlEditor
102065  * @extends Ext.layout.component.field.Field
102066  * Layout class for {@link Ext.form.field.HtmlEditor} fields. Sizes the toolbar, textarea, and iframe elements.
102067  * @private
102068  */
102069
102070 Ext.define('Ext.layout.component.field.HtmlEditor', {
102071     extend: 'Ext.layout.component.field.Field',
102072     alias: ['layout.htmleditor'],
102073
102074     type: 'htmleditor',
102075
102076     sizeBodyContents: function(width, height) {
102077         var me = this,
102078             owner = me.owner,
102079             bodyEl = owner.bodyEl,
102080             toolbar = owner.getToolbar(),
102081             textarea = owner.textareaEl,
102082             iframe = owner.iframeEl,
102083             editorHeight;
102084
102085         if (Ext.isNumber(width)) {
102086             width -= bodyEl.getFrameWidth('lr');
102087         }
102088         toolbar.setWidth(width);
102089         textarea.setWidth(width);
102090         iframe.setWidth(width);
102091
102092         // If fixed height, subtract toolbar height from the input area height
102093         if (Ext.isNumber(height)) {
102094             editorHeight = height - toolbar.getHeight() - bodyEl.getFrameWidth('tb');
102095             textarea.setHeight(editorHeight);
102096             iframe.setHeight(editorHeight);
102097         }
102098     }
102099 });
102100 /**
102101  * @class Ext.form.field.HtmlEditor
102102  * @extends Ext.Component
102103  *
102104  * Provides a lightweight HTML Editor component. Some toolbar features are not supported by Safari and will be
102105  * automatically hidden when needed. These are noted in the config options where appropriate.
102106  * 
102107  * The editor's toolbar buttons have tooltips defined in the {@link #buttonTips} property, but they are not
102108  * enabled by default unless the global {@link Ext.tip.QuickTipManager} singleton is {@link Ext.tip.QuickTipManager#init initialized}.
102109  * 
102110  * An Editor is a sensitive component that can't be used in all spots standard fields can be used. Putting an Editor within
102111  * any element that has display set to 'none' can cause problems in Safari and Firefox due to their default iframe reloading bugs.
102112  *
102113  * {@img Ext.form.HtmlEditor/Ext.form.HtmlEditor1.png Ext.form.HtmlEditor component}
102114  *
102115  * ## Example usage
102116  *
102117  * {@img Ext.form.HtmlEditor/Ext.form.HtmlEditor2.png Ext.form.HtmlEditor component}
102118  *
102119  *     // Simple example rendered with default options:
102120  *     Ext.tip.QuickTips.init();  // enable tooltips
102121  *     Ext.create('Ext.form.HtmlEditor', {
102122  *         width: 580,
102123  *         height: 250,
102124  *         renderTo: Ext.getBody()        
102125  *     });
102126  * 
102127  * {@img Ext.form.HtmlEditor/Ext.form.HtmlEditor2.png Ext.form.HtmlEditor component}
102128  * 
102129  *     // Passed via xtype into a container and with custom options:
102130  *     Ext.tip.QuickTips.init();  // enable tooltips
102131  *     new Ext.panel.Panel({
102132  *         title: 'HTML Editor',
102133  *         renderTo: Ext.getBody(),
102134  *         width: 550,
102135  *         height: 250,
102136  *         frame: true,
102137  *         layout: 'fit',
102138  *         items: {
102139  *             xtype: 'htmleditor',
102140  *             enableColors: false,
102141  *             enableAlignments: false
102142  *         }
102143  *     });
102144  *
102145  * @constructor
102146  * Create a new HtmlEditor
102147  * @param {Object} config
102148  * @xtype htmleditor
102149  */
102150 Ext.define('Ext.form.field.HtmlEditor', {
102151     extend:'Ext.Component',
102152     mixins: {
102153         labelable: 'Ext.form.Labelable',
102154         field: 'Ext.form.field.Field'
102155     },
102156     alias: 'widget.htmleditor',
102157     alternateClassName: 'Ext.form.HtmlEditor',
102158     requires: [
102159         'Ext.tip.QuickTipManager',
102160         'Ext.picker.Color',
102161         'Ext.toolbar.Item',
102162         'Ext.toolbar.Toolbar',
102163         'Ext.util.Format',
102164         'Ext.layout.component.field.HtmlEditor'
102165     ],
102166
102167     fieldSubTpl: [
102168         '<div class="{toolbarWrapCls}"></div>',
102169         '<textarea id="{id}" name="{name}" tabIndex="-1" class="{textareaCls}" ',
102170             'style="{size}" autocomplete="off"></textarea>',
102171         '<iframe name="{iframeName}" frameBorder="0" style="overflow:auto;{size}" src="{iframeSrc}"></iframe>',
102172         {
102173             compiled: true,
102174             disableFormats: true
102175         }
102176     ],
102177
102178     /**
102179      * @cfg {Boolean} enableFormat Enable the bold, italic and underline buttons (defaults to true)
102180      */
102181     enableFormat : true,
102182     /**
102183      * @cfg {Boolean} enableFontSize Enable the increase/decrease font size buttons (defaults to true)
102184      */
102185     enableFontSize : true,
102186     /**
102187      * @cfg {Boolean} enableColors Enable the fore/highlight color buttons (defaults to true)
102188      */
102189     enableColors : true,
102190     /**
102191      * @cfg {Boolean} enableAlignments Enable the left, center, right alignment buttons (defaults to true)
102192      */
102193     enableAlignments : true,
102194     /**
102195      * @cfg {Boolean} enableLists Enable the bullet and numbered list buttons. Not available in Safari. (defaults to true)
102196      */
102197     enableLists : true,
102198     /**
102199      * @cfg {Boolean} enableSourceEdit Enable the switch to source edit button. Not available in Safari. (defaults to true)
102200      */
102201     enableSourceEdit : true,
102202     /**
102203      * @cfg {Boolean} enableLinks Enable the create link button. Not available in Safari. (defaults to true)
102204      */
102205     enableLinks : true,
102206     /**
102207      * @cfg {Boolean} enableFont Enable font selection. Not available in Safari. (defaults to true)
102208      */
102209     enableFont : true,
102210     /**
102211      * @cfg {String} createLinkText The default text for the create link prompt
102212      */
102213     createLinkText : 'Please enter the URL for the link:',
102214     /**
102215      * @cfg {String} defaultLinkValue The default value for the create link prompt (defaults to http:/ /)
102216      */
102217     defaultLinkValue : 'http:/'+'/',
102218     /**
102219      * @cfg {Array} fontFamilies An array of available font families
102220      */
102221     fontFamilies : [
102222         'Arial',
102223         'Courier New',
102224         'Tahoma',
102225         'Times New Roman',
102226         'Verdana'
102227     ],
102228     defaultFont: 'tahoma',
102229     /**
102230      * @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).
102231      */
102232     defaultValue: (Ext.isOpera || Ext.isIE6) ? '&#160;' : '&#8203;',
102233
102234     fieldBodyCls: Ext.baseCSSPrefix + 'html-editor-wrap',
102235
102236     componentLayout: 'htmleditor',
102237
102238     // private properties
102239     initialized : false,
102240     activated : false,
102241     sourceEditMode : false,
102242     iframePad:3,
102243     hideMode:'offsets',
102244
102245     maskOnDisable: true,
102246     
102247     // private
102248     initComponent : function(){
102249         var me = this;
102250
102251         me.addEvents(
102252             /**
102253              * @event initialize
102254              * Fires when the editor is fully initialized (including the iframe)
102255              * @param {Ext.form.field.HtmlEditor} this
102256              */
102257             'initialize',
102258             /**
102259              * @event activate
102260              * Fires when the editor is first receives the focus. Any insertion must wait
102261              * until after this event.
102262              * @param {Ext.form.field.HtmlEditor} this
102263              */
102264             'activate',
102265              /**
102266              * @event beforesync
102267              * Fires before the textarea is updated with content from the editor iframe. Return false
102268              * to cancel the sync.
102269              * @param {Ext.form.field.HtmlEditor} this
102270              * @param {String} html
102271              */
102272             'beforesync',
102273              /**
102274              * @event beforepush
102275              * Fires before the iframe editor is updated with content from the textarea. Return false
102276              * to cancel the push.
102277              * @param {Ext.form.field.HtmlEditor} this
102278              * @param {String} html
102279              */
102280             'beforepush',
102281              /**
102282              * @event sync
102283              * Fires when the textarea is updated with content from the editor iframe.
102284              * @param {Ext.form.field.HtmlEditor} this
102285              * @param {String} html
102286              */
102287             'sync',
102288              /**
102289              * @event push
102290              * Fires when the iframe editor is updated with content from the textarea.
102291              * @param {Ext.form.field.HtmlEditor} this
102292              * @param {String} html
102293              */
102294             'push',
102295              /**
102296              * @event editmodechange
102297              * Fires when the editor switches edit modes
102298              * @param {Ext.form.field.HtmlEditor} this
102299              * @param {Boolean} sourceEdit True if source edit, false if standard editing.
102300              */
102301             'editmodechange'
102302         );
102303
102304         me.callParent(arguments);
102305
102306         // Init mixins
102307         me.initLabelable();
102308         me.initField();
102309     },
102310
102311     /*
102312      * Protected method that will not generally be called directly. It
102313      * is called when the editor creates its toolbar. Override this method if you need to
102314      * add custom toolbar buttons.
102315      * @param {Ext.form.field.HtmlEditor} editor
102316      */
102317     createToolbar : function(editor){
102318         var me = this,
102319             items = [],
102320             tipsEnabled = Ext.tip.QuickTipManager && Ext.tip.QuickTipManager.isEnabled(),
102321             baseCSSPrefix = Ext.baseCSSPrefix,
102322             fontSelectItem, toolbar, undef;
102323
102324         function btn(id, toggle, handler){
102325             return {
102326                 itemId : id,
102327                 cls : baseCSSPrefix + 'btn-icon',
102328                 iconCls: baseCSSPrefix + 'edit-'+id,
102329                 enableToggle:toggle !== false,
102330                 scope: editor,
102331                 handler:handler||editor.relayBtnCmd,
102332                 clickEvent:'mousedown',
102333                 tooltip: tipsEnabled ? editor.buttonTips[id] || undef : undef,
102334                 overflowText: editor.buttonTips[id].title || undef,
102335                 tabIndex:-1
102336             };
102337         }
102338
102339
102340         if (me.enableFont && !Ext.isSafari2) {
102341             fontSelectItem = Ext.widget('component', {
102342                 renderTpl: [
102343                     '<select class="{cls}">',
102344                         '<tpl for="fonts">',
102345                             '<option value="{[values.toLowerCase()]}" style="font-family:{.}"<tpl if="values.toLowerCase()==parent.defaultFont"> selected</tpl>>{.}</option>',
102346                         '</tpl>',
102347                     '</select>'
102348                 ],
102349                 renderData: {
102350                     cls: baseCSSPrefix + 'font-select',
102351                     fonts: me.fontFamilies,
102352                     defaultFont: me.defaultFont
102353                 },
102354                 renderSelectors: {
102355                     selectEl: 'select'
102356                 },
102357                 onDisable: function() {
102358                     var selectEl = this.selectEl;
102359                     if (selectEl) {
102360                         selectEl.dom.disabled = true;
102361                     }
102362                     Ext.Component.superclass.onDisable.apply(this, arguments);
102363                 },
102364                 onEnable: function() {
102365                     var selectEl = this.selectEl;
102366                     if (selectEl) {
102367                         selectEl.dom.disabled = false;
102368                     }
102369                     Ext.Component.superclass.onEnable.apply(this, arguments);
102370                 }
102371             });
102372
102373             items.push(
102374                 fontSelectItem,
102375                 '-'
102376             );
102377         }
102378
102379         if (me.enableFormat) {
102380             items.push(
102381                 btn('bold'),
102382                 btn('italic'),
102383                 btn('underline')
102384             );
102385         }
102386
102387         if (me.enableFontSize) {
102388             items.push(
102389                 '-',
102390                 btn('increasefontsize', false, me.adjustFont),
102391                 btn('decreasefontsize', false, me.adjustFont)
102392             );
102393         }
102394
102395         if (me.enableColors) {
102396             items.push(
102397                 '-', {
102398                     itemId: 'forecolor',
102399                     cls: baseCSSPrefix + 'btn-icon',
102400                     iconCls: baseCSSPrefix + 'edit-forecolor',
102401                     overflowText: editor.buttonTips.forecolor.title,
102402                     tooltip: tipsEnabled ? editor.buttonTips.forecolor || undef : undef,
102403                     tabIndex:-1,
102404                     menu : Ext.widget('menu', {
102405                         plain: true,
102406                         items: [{
102407                             xtype: 'colorpicker',
102408                             allowReselect: true,
102409                             focus: Ext.emptyFn,
102410                             value: '000000',
102411                             plain: true,
102412                             clickEvent: 'mousedown',
102413                             handler: function(cp, color) {
102414                                 me.execCmd('forecolor', Ext.isWebKit || Ext.isIE ? '#'+color : color);
102415                                 me.deferFocus();
102416                                 this.up('menu').hide();
102417                             }
102418                         }]
102419                     })
102420                 }, {
102421                     itemId: 'backcolor',
102422                     cls: baseCSSPrefix + 'btn-icon',
102423                     iconCls: baseCSSPrefix + 'edit-backcolor',
102424                     overflowText: editor.buttonTips.backcolor.title,
102425                     tooltip: tipsEnabled ? editor.buttonTips.backcolor || undef : undef,
102426                     tabIndex:-1,
102427                     menu : Ext.widget('menu', {
102428                         plain: true,
102429                         items: [{
102430                             xtype: 'colorpicker',
102431                             focus: Ext.emptyFn,
102432                             value: 'FFFFFF',
102433                             plain: true,
102434                             allowReselect: true,
102435                             clickEvent: 'mousedown',
102436                             handler: function(cp, color) {
102437                                 if (Ext.isGecko) {
102438                                     me.execCmd('useCSS', false);
102439                                     me.execCmd('hilitecolor', color);
102440                                     me.execCmd('useCSS', true);
102441                                     me.deferFocus();
102442                                 } else {
102443                                     me.execCmd(Ext.isOpera ? 'hilitecolor' : 'backcolor', Ext.isWebKit || Ext.isIE ? '#'+color : color);
102444                                     me.deferFocus();
102445                                 }
102446                                 this.up('menu').hide();
102447                             }
102448                         }]
102449                     })
102450                 }
102451             );
102452         }
102453
102454         if (me.enableAlignments) {
102455             items.push(
102456                 '-',
102457                 btn('justifyleft'),
102458                 btn('justifycenter'),
102459                 btn('justifyright')
102460             );
102461         }
102462
102463         if (!Ext.isSafari2) {
102464             if (me.enableLinks) {
102465                 items.push(
102466                     '-',
102467                     btn('createlink', false, me.createLink)
102468                 );
102469             }
102470
102471             if (me.enableLists) {
102472                 items.push(
102473                     '-',
102474                     btn('insertorderedlist'),
102475                     btn('insertunorderedlist')
102476                 );
102477             }
102478             if (me.enableSourceEdit) {
102479                 items.push(
102480                     '-',
102481                     btn('sourceedit', true, function(btn){
102482                         me.toggleSourceEdit(!me.sourceEditMode);
102483                     })
102484                 );
102485             }
102486         }
102487
102488         // build the toolbar
102489         toolbar = Ext.widget('toolbar', {
102490             renderTo: me.toolbarWrap,
102491             enableOverflow: true,
102492             items: items
102493         });
102494
102495         if (fontSelectItem) {
102496             me.fontSelect = fontSelectItem.selectEl;
102497
102498             me.mon(me.fontSelect, 'change', function(){
102499                 me.relayCmd('fontname', me.fontSelect.dom.value);
102500                 me.deferFocus();
102501             });
102502         }
102503
102504         // stop form submits
102505         me.mon(toolbar.el, 'click', function(e){
102506             e.preventDefault();
102507         });
102508
102509         me.toolbar = toolbar;
102510     },
102511
102512     onDisable: function() {
102513         this.bodyEl.mask();
102514         this.callParent(arguments);
102515     },
102516
102517     onEnable: function() {
102518         this.bodyEl.unmask();
102519         this.callParent(arguments);
102520     },
102521
102522     /**
102523      * Sets the read only state of this field.
102524      * @param {Boolean} readOnly Whether the field should be read only.
102525      */
102526     setReadOnly: function(readOnly) {
102527         var me = this,
102528             textareaEl = me.textareaEl,
102529             iframeEl = me.iframeEl,
102530             body;
102531
102532         me.readOnly = readOnly;
102533
102534         if (textareaEl) {
102535             textareaEl.dom.readOnly = readOnly;
102536         }
102537
102538         if (me.initialized) {
102539             body = me.getEditorBody();
102540             if (Ext.isIE) {
102541                 // Hide the iframe while setting contentEditable so it doesn't grab focus
102542                 iframeEl.setDisplayed(false);
102543                 body.contentEditable = !readOnly;
102544                 iframeEl.setDisplayed(true);
102545             } else {
102546                 me.setDesignMode(!readOnly);
102547             }
102548             if (body) {
102549                 body.style.cursor = readOnly ? 'default' : 'text';
102550             }
102551             me.disableItems(readOnly);
102552         }
102553     },
102554
102555     /**
102556      * Protected method that will not generally be called directly. It
102557      * is called when the editor initializes the iframe with HTML contents. Override this method if you
102558      * want to change the initialization markup of the iframe (e.g. to add stylesheets).
102559      *
102560      * Note: IE8-Standards has unwanted scroller behavior, so the default meta tag forces IE7 compatibility.
102561      * Also note that forcing IE7 mode works when the page is loaded normally, but if you are using IE's Web
102562      * Developer Tools to manually set the document mode, that will take precedence and override what this
102563      * code sets by default. This can be confusing when developing, but is not a user-facing issue.
102564      */
102565     getDocMarkup: function() {
102566         var me = this,
102567             h = me.iframeEl.getHeight() - me.iframePad * 2;
102568         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);
102569     },
102570
102571     // private
102572     getEditorBody: function() {
102573         var doc = this.getDoc();
102574         return doc.body || doc.documentElement;
102575     },
102576
102577     // private
102578     getDoc: function() {
102579         return (!Ext.isIE && this.iframeEl.dom.contentDocument) || this.getWin().document;
102580     },
102581
102582     // private
102583     getWin: function() {
102584         return Ext.isIE ? this.iframeEl.dom.contentWindow : window.frames[this.iframeEl.dom.name];
102585     },
102586
102587     // private
102588     onRender: function() {
102589         var me = this,
102590             renderSelectors = me.renderSelectors;
102591
102592         Ext.applyIf(renderSelectors, me.getLabelableSelectors());
102593
102594         Ext.applyIf(renderSelectors, {
102595             toolbarWrap: 'div.' + Ext.baseCSSPrefix + 'html-editor-tb',
102596             iframeEl: 'iframe',
102597             textareaEl: 'textarea'
102598         });
102599
102600         me.callParent(arguments);
102601
102602         me.textareaEl.dom.value = me.value || '';
102603
102604         // Start polling for when the iframe document is ready to be manipulated
102605         me.monitorTask = Ext.TaskManager.start({
102606             run: me.checkDesignMode,
102607             scope: me,
102608             interval:100
102609         });
102610
102611         me.createToolbar(me);
102612         me.disableItems(true);
102613     },
102614
102615     initRenderTpl: function() {
102616         var me = this;
102617         if (!me.hasOwnProperty('renderTpl')) {
102618             me.renderTpl = me.getTpl('labelableRenderTpl');
102619         }
102620         return me.callParent();
102621     },
102622
102623     initRenderData: function() {
102624         return Ext.applyIf(this.callParent(), this.getLabelableRenderData());
102625     },
102626
102627     getSubTplData: function() {
102628         var cssPrefix = Ext.baseCSSPrefix;
102629         return {
102630             toolbarWrapCls: cssPrefix + 'html-editor-tb',
102631             textareaCls: cssPrefix + 'hidden',
102632             iframeName: Ext.id(),
102633             iframeSrc: Ext.SSL_SECURE_URL,
102634             size: 'height:100px;'
102635         };
102636     },
102637
102638     getSubTplMarkup: function() {
102639         return this.getTpl('fieldSubTpl').apply(this.getSubTplData());
102640     },
102641
102642     getBodyNaturalWidth: function() {
102643         return 565;
102644     },
102645
102646     initFrameDoc: function() {
102647         var me = this,
102648             doc, task;
102649
102650         Ext.TaskManager.stop(me.monitorTask);
102651
102652         doc = me.getDoc();
102653         me.win = me.getWin();
102654
102655         doc.open();
102656         doc.write(me.getDocMarkup());
102657         doc.close();
102658
102659         task = { // must defer to wait for browser to be ready
102660             run: function() {
102661                 var doc = me.getDoc();
102662                 if (doc.body || doc.readyState === 'complete') {
102663                     Ext.TaskManager.stop(task);
102664                     me.setDesignMode(true);
102665                     Ext.defer(me.initEditor, 10, me);
102666                 }
102667             },
102668             interval : 10,
102669             duration:10000,
102670             scope: me
102671         };
102672         Ext.TaskManager.start(task);
102673     },
102674
102675     checkDesignMode: function() {
102676         var me = this,
102677             doc = me.getDoc();
102678         if (doc && (!doc.editorInitialized || me.getDesignMode() !== 'on')) {
102679             me.initFrameDoc();
102680         }
102681     },
102682
102683     /* private
102684      * set current design mode. To enable, mode can be true or 'on', off otherwise
102685      */
102686     setDesignMode: function(mode) {
102687         var me = this,
102688             doc = me.getDoc();
102689         if (doc) {
102690             if (me.readOnly) {
102691                 mode = false;
102692             }
102693             doc.designMode = (/on|true/i).test(String(mode).toLowerCase()) ?'on':'off';
102694         }
102695     },
102696
102697     // private
102698     getDesignMode: function() {
102699         var doc = this.getDoc();
102700         return !doc ? '' : String(doc.designMode).toLowerCase();
102701     },
102702
102703     disableItems: function(disabled) {
102704         this.getToolbar().items.each(function(item){
102705             if(item.getItemId() !== 'sourceedit'){
102706                 item.setDisabled(disabled);
102707             }
102708         });
102709     },
102710
102711     /**
102712      * Toggles the editor between standard and source edit mode.
102713      * @param {Boolean} sourceEditMode (optional) True for source edit, false for standard
102714      */
102715     toggleSourceEdit: function(sourceEditMode) {
102716         var me = this,
102717             iframe = me.iframeEl,
102718             textarea = me.textareaEl,
102719             hiddenCls = Ext.baseCSSPrefix + 'hidden',
102720             btn = me.getToolbar().getComponent('sourceedit');
102721
102722         if (!Ext.isBoolean(sourceEditMode)) {
102723             sourceEditMode = !me.sourceEditMode;
102724         }
102725         me.sourceEditMode = sourceEditMode;
102726
102727         if (btn.pressed !== sourceEditMode) {
102728             btn.toggle(sourceEditMode);
102729         }
102730         if (sourceEditMode) {
102731             me.disableItems(true);
102732             me.syncValue();
102733             iframe.addCls(hiddenCls);
102734             textarea.removeCls(hiddenCls);
102735             textarea.dom.removeAttribute('tabIndex');
102736             textarea.focus();
102737         }
102738         else {
102739             if (me.initialized) {
102740                 me.disableItems(me.readOnly);
102741             }
102742             me.pushValue();
102743             iframe.removeCls(hiddenCls);
102744             textarea.addCls(hiddenCls);
102745             textarea.dom.setAttribute('tabIndex', -1);
102746             me.deferFocus();
102747         }
102748         me.fireEvent('editmodechange', me, sourceEditMode);
102749         me.doComponentLayout();
102750     },
102751
102752     // private used internally
102753     createLink : function() {
102754         var url = prompt(this.createLinkText, this.defaultLinkValue);
102755         if (url && url !== 'http:/'+'/') {
102756             this.relayCmd('createlink', url);
102757         }
102758     },
102759
102760     clearInvalid: Ext.emptyFn,
102761
102762     // docs inherit from Field
102763     setValue: function(value) {
102764         var me = this,
102765             textarea = me.textareaEl;
102766         me.mixins.field.setValue.call(me, value);
102767         if (value === null || value === undefined) {
102768             value = '';
102769         }
102770         if (textarea) {
102771             textarea.dom.value = value;
102772         }
102773         me.pushValue();
102774         return this;
102775     },
102776
102777     /**
102778      * Protected method that will not generally be called directly. If you need/want
102779      * custom HTML cleanup, this is the method you should override.
102780      * @param {String} html The HTML to be cleaned
102781      * @return {String} The cleaned HTML
102782      */
102783     cleanHtml: function(html) {
102784         html = String(html);
102785         if (Ext.isWebKit) { // strip safari nonsense
102786             html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, '');
102787         }
102788
102789         /*
102790          * Neat little hack. Strips out all the non-digit characters from the default
102791          * value and compares it to the character code of the first character in the string
102792          * because it can cause encoding issues when posted to the server.
102793          */
102794         if (html.charCodeAt(0) === this.defaultValue.replace(/\D/g, '')) {
102795             html = html.substring(1);
102796         }
102797         return html;
102798     },
102799
102800     /**
102801      * @protected method that will not generally be called directly. Syncs the contents
102802      * of the editor iframe with the textarea.
102803      */
102804     syncValue : function(){
102805         var me = this,
102806             body, html, bodyStyle, match;
102807         if (me.initialized) {
102808             body = me.getEditorBody();
102809             html = body.innerHTML;
102810             if (Ext.isWebKit) {
102811                 bodyStyle = body.getAttribute('style'); // Safari puts text-align styles on the body element!
102812                 match = bodyStyle.match(/text-align:(.*?);/i);
102813                 if (match && match[1]) {
102814                     html = '<div style="' + match[0] + '">' + html + '</div>';
102815                 }
102816             }
102817             html = me.cleanHtml(html);
102818             if (me.fireEvent('beforesync', me, html) !== false) {
102819                 me.textareaEl.dom.value = html;
102820                 me.fireEvent('sync', me, html);
102821             }
102822         }
102823     },
102824
102825     //docs inherit from Field
102826     getValue : function() {
102827         var me = this,
102828             value;
102829         if (!me.sourceEditMode) {
102830             me.syncValue();
102831         }
102832         value = me.rendered ? me.textareaEl.dom.value : me.value;
102833         me.value = value;
102834         return value;
102835     },
102836
102837     /**
102838      * @protected method that will not generally be called directly. Pushes the value of the textarea
102839      * into the iframe editor.
102840      */
102841     pushValue: function() {
102842         var me = this,
102843             v;
102844         if(me.initialized){
102845             v = me.textareaEl.dom.value || '';
102846             if (!me.activated && v.length < 1) {
102847                 v = me.defaultValue;
102848             }
102849             if (me.fireEvent('beforepush', me, v) !== false) {
102850                 me.getEditorBody().innerHTML = v;
102851                 if (Ext.isGecko) {
102852                     // Gecko hack, see: https://bugzilla.mozilla.org/show_bug.cgi?id=232791#c8
102853                     me.setDesignMode(false);  //toggle off first
102854                     me.setDesignMode(true);
102855                 }
102856                 me.fireEvent('push', me, v);
102857             }
102858         }
102859     },
102860
102861     // private
102862     deferFocus : function(){
102863          this.focus(false, true);
102864     },
102865
102866     getFocusEl: function() {
102867         var me = this,
102868             win = me.win;
102869         return win && !me.sourceEditMode ? win : me.textareaEl;
102870     },
102871
102872     // private
102873     initEditor : function(){
102874         //Destroying the component during/before initEditor can cause issues.
102875         try {
102876             var me = this,
102877                 dbody = me.getEditorBody(),
102878                 ss = me.textareaEl.getStyles('font-size', 'font-family', 'background-image', 'background-repeat', 'background-color', 'color'),
102879                 doc,
102880                 fn;
102881
102882             ss['background-attachment'] = 'fixed'; // w3c
102883             dbody.bgProperties = 'fixed'; // ie
102884
102885             Ext.core.DomHelper.applyStyles(dbody, ss);
102886
102887             doc = me.getDoc();
102888
102889             if (doc) {
102890                 try {
102891                     Ext.EventManager.removeAll(doc);
102892                 } catch(e) {}
102893             }
102894
102895             /*
102896              * We need to use createDelegate here, because when using buffer, the delayed task is added
102897              * as a property to the function. When the listener is removed, the task is deleted from the function.
102898              * Since onEditorEvent is shared on the prototype, if we have multiple html editors, the first time one of the editors
102899              * is destroyed, it causes the fn to be deleted from the prototype, which causes errors. Essentially, we're just anonymizing the function.
102900              */
102901             fn = Ext.Function.bind(me.onEditorEvent, me);
102902             Ext.EventManager.on(doc, {
102903                 mousedown: fn,
102904                 dblclick: fn,
102905                 click: fn,
102906                 keyup: fn,
102907                 buffer:100
102908             });
102909
102910             // These events need to be relayed from the inner document (where they stop
102911             // bubbling) up to the outer document. This has to be done at the DOM level so
102912             // the event reaches listeners on elements like the document body. The effected
102913             // mechanisms that depend on this bubbling behavior are listed to the right
102914             // of the event.
102915             fn = me.onRelayedEvent;
102916             Ext.EventManager.on(doc, {
102917                 mousedown: fn, // menu dismisal (MenuManager) and Window onMouseDown (toFront)
102918                 mousemove: fn, // window resize drag detection
102919                 mouseup: fn,   // window resize termination
102920                 click: fn,     // not sure, but just to be safe
102921                 dblclick: fn,  // not sure again
102922                 scope: me
102923             });
102924
102925             if (Ext.isGecko) {
102926                 Ext.EventManager.on(doc, 'keypress', me.applyCommand, me);
102927             }
102928             if (me.fixKeys) {
102929                 Ext.EventManager.on(doc, 'keydown', me.fixKeys, me);
102930             }
102931
102932             // We need to be sure we remove all our events from the iframe on unload or we're going to LEAK!
102933             Ext.EventManager.on(window, 'unload', me.beforeDestroy, me);
102934             doc.editorInitialized = true;
102935
102936             me.initialized = true;
102937             me.pushValue();
102938             me.setReadOnly(me.readOnly);
102939             me.fireEvent('initialize', me);
102940         } catch(ex) {
102941             // ignore (why?)
102942         }
102943     },
102944
102945     // private
102946     beforeDestroy : function(){
102947         var me = this,
102948             monitorTask = me.monitorTask,
102949             doc, prop;
102950
102951         if (monitorTask) {
102952             Ext.TaskManager.stop(monitorTask);
102953         }
102954         if (me.rendered) {
102955             try {
102956                 doc = me.getDoc();
102957                 if (doc) {
102958                     Ext.EventManager.removeAll(doc);
102959                     for (prop in doc) {
102960                         if (doc.hasOwnProperty(prop)) {
102961                             delete doc[prop];
102962                         }
102963                     }
102964                 }
102965             } catch(e) {
102966                 // ignore (why?)
102967             }
102968             Ext.destroyMembers('tb', 'toolbarWrap', 'iframeEl', 'textareaEl');
102969         }
102970         me.callParent();
102971     },
102972
102973     // private
102974     onRelayedEvent: function (event) {
102975         // relay event from the iframe's document to the document that owns the iframe...
102976
102977         var iframeEl = this.iframeEl,
102978             iframeXY = iframeEl.getXY(),
102979             eventXY = event.getXY();
102980
102981         // the event from the inner document has XY relative to that document's origin,
102982         // so adjust it to use the origin of the iframe in the outer document:
102983         event.xy = [iframeXY[0] + eventXY[0], iframeXY[1] + eventXY[1]];
102984
102985         event.injectEvent(iframeEl); // blame the iframe for the event...
102986
102987         event.xy = eventXY; // restore the original XY (just for safety)
102988     },
102989
102990     // private
102991     onFirstFocus : function(){
102992         var me = this,
102993             selection, range;
102994         me.activated = true;
102995         me.disableItems(me.readOnly);
102996         if (Ext.isGecko) { // prevent silly gecko errors
102997             me.win.focus();
102998             selection = me.win.getSelection();
102999             if (!selection.focusNode || selection.focusNode.nodeType !== 3) {
103000                 range = selection.getRangeAt(0);
103001                 range.selectNodeContents(me.getEditorBody());
103002                 range.collapse(true);
103003                 me.deferFocus();
103004             }
103005             try {
103006                 me.execCmd('useCSS', true);
103007                 me.execCmd('styleWithCSS', false);
103008             } catch(e) {
103009                 // ignore (why?)
103010             }
103011         }
103012         me.fireEvent('activate', me);
103013     },
103014
103015     // private
103016     adjustFont: function(btn) {
103017         var adjust = btn.getItemId() === 'increasefontsize' ? 1 : -1,
103018             size = this.getDoc().queryCommandValue('FontSize') || '2',
103019             isPxSize = Ext.isString(size) && size.indexOf('px') !== -1,
103020             isSafari;
103021         size = parseInt(size, 10);
103022         if (isPxSize) {
103023             // Safari 3 values
103024             // 1 = 10px, 2 = 13px, 3 = 16px, 4 = 18px, 5 = 24px, 6 = 32px
103025             if (size <= 10) {
103026                 size = 1 + adjust;
103027             }
103028             else if (size <= 13) {
103029                 size = 2 + adjust;
103030             }
103031             else if (size <= 16) {
103032                 size = 3 + adjust;
103033             }
103034             else if (size <= 18) {
103035                 size = 4 + adjust;
103036             }
103037             else if (size <= 24) {
103038                 size = 5 + adjust;
103039             }
103040             else {
103041                 size = 6 + adjust;
103042             }
103043             size = Ext.Number.constrain(size, 1, 6);
103044         } else {
103045             isSafari = Ext.isSafari;
103046             if (isSafari) { // safari
103047                 adjust *= 2;
103048             }
103049             size = Math.max(1, size + adjust) + (isSafari ? 'px' : 0);
103050         }
103051         this.execCmd('FontSize', size);
103052     },
103053
103054     // private
103055     onEditorEvent: function(e) {
103056         this.updateToolbar();
103057     },
103058
103059     /**
103060      * Protected method that will not generally be called directly. It triggers
103061      * a toolbar update by reading the markup state of the current selection in the editor.
103062      */
103063     updateToolbar: function() {
103064         var me = this,
103065             btns, doc, name, fontSelect;
103066
103067         if (me.readOnly) {
103068             return;
103069         }
103070
103071         if (!me.activated) {
103072             me.onFirstFocus();
103073             return;
103074         }
103075
103076         btns = me.getToolbar().items.map;
103077         doc = me.getDoc();
103078
103079         if (me.enableFont && !Ext.isSafari2) {
103080             name = (doc.queryCommandValue('FontName') || me.defaultFont).toLowerCase();
103081             fontSelect = me.fontSelect.dom;
103082             if (name !== fontSelect.value) {
103083                 fontSelect.value = name;
103084             }
103085         }
103086
103087         function updateButtons() {
103088             Ext.Array.forEach(Ext.Array.toArray(arguments), function(name) {
103089                 btns[name].toggle(doc.queryCommandState(name));
103090             });
103091         }
103092         if(me.enableFormat){
103093             updateButtons('bold', 'italic', 'underline');
103094         }
103095         if(me.enableAlignments){
103096             updateButtons('justifyleft', 'justifycenter', 'justifyright');
103097         }
103098         if(!Ext.isSafari2 && me.enableLists){
103099             updateButtons('insertorderedlist', 'insertunorderedlist');
103100         }
103101
103102         Ext.menu.Manager.hideAll();
103103
103104         me.syncValue();
103105     },
103106
103107     // private
103108     relayBtnCmd: function(btn) {
103109         this.relayCmd(btn.getItemId());
103110     },
103111
103112     /**
103113      * Executes a Midas editor command on the editor document and performs necessary focus and
103114      * toolbar updates. <b>This should only be called after the editor is initialized.</b>
103115      * @param {String} cmd The Midas command
103116      * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
103117      */
103118     relayCmd: function(cmd, value) {
103119         Ext.defer(function() {
103120             var me = this;
103121             me.focus();
103122             me.execCmd(cmd, value);
103123             me.updateToolbar();
103124         }, 10, this);
103125     },
103126
103127     /**
103128      * Executes a Midas editor command directly on the editor document.
103129      * For visual commands, you should use {@link #relayCmd} instead.
103130      * <b>This should only be called after the editor is initialized.</b>
103131      * @param {String} cmd The Midas command
103132      * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
103133      */
103134     execCmd : function(cmd, value){
103135         var me = this,
103136             doc = me.getDoc(),
103137             undef;
103138         doc.execCommand(cmd, false, value === undef ? null : value);
103139         me.syncValue();
103140     },
103141
103142     // private
103143     applyCommand : function(e){
103144         if (e.ctrlKey) {
103145             var me = this,
103146                 c = e.getCharCode(), cmd;
103147             if (c > 0) {
103148                 c = String.fromCharCode(c);
103149                 switch (c) {
103150                     case 'b':
103151                         cmd = 'bold';
103152                     break;
103153                     case 'i':
103154                         cmd = 'italic';
103155                     break;
103156                     case 'u':
103157                         cmd = 'underline';
103158                     break;
103159                 }
103160                 if (cmd) {
103161                     me.win.focus();
103162                     me.execCmd(cmd);
103163                     me.deferFocus();
103164                     e.preventDefault();
103165                 }
103166             }
103167         }
103168     },
103169
103170     /**
103171      * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated
103172      * to insert text.
103173      * @param {String} text
103174      */
103175     insertAtCursor : function(text){
103176         var me = this,
103177             range;
103178
103179         if (me.activated) {
103180             me.win.focus();
103181             if (Ext.isIE) {
103182                 range = me.getDoc().selection.createRange();
103183                 if (range) {
103184                     range.pasteHTML(text);
103185                     me.syncValue();
103186                     me.deferFocus();
103187                 }
103188             }else{
103189                 me.execCmd('InsertHTML', text);
103190                 me.deferFocus();
103191             }
103192         }
103193     },
103194
103195     // private
103196     fixKeys: function() { // load time branching for fastest keydown performance
103197         if (Ext.isIE) {
103198             return function(e){
103199                 var me = this,
103200                     k = e.getKey(),
103201                     doc = me.getDoc(),
103202                     range, target;
103203                 if (k === e.TAB) {
103204                     e.stopEvent();
103205                     range = doc.selection.createRange();
103206                     if(range){
103207                         range.collapse(true);
103208                         range.pasteHTML('&nbsp;&nbsp;&nbsp;&nbsp;');
103209                         me.deferFocus();
103210                     }
103211                 }
103212                 else if (k === e.ENTER) {
103213                     range = doc.selection.createRange();
103214                     if (range) {
103215                         target = range.parentElement();
103216                         if(!target || target.tagName.toLowerCase() !== 'li'){
103217                             e.stopEvent();
103218                             range.pasteHTML('<br />');
103219                             range.collapse(false);
103220                             range.select();
103221                         }
103222                     }
103223                 }
103224             };
103225         }
103226
103227         if (Ext.isOpera) {
103228             return function(e){
103229                 var me = this;
103230                 if (e.getKey() === e.TAB) {
103231                     e.stopEvent();
103232                     me.win.focus();
103233                     me.execCmd('InsertHTML','&nbsp;&nbsp;&nbsp;&nbsp;');
103234                     me.deferFocus();
103235                 }
103236             };
103237         }
103238
103239         if (Ext.isWebKit) {
103240             return function(e){
103241                 var me = this,
103242                     k = e.getKey();
103243                 if (k === e.TAB) {
103244                     e.stopEvent();
103245                     me.execCmd('InsertText','\t');
103246                     me.deferFocus();
103247                 }
103248                 else if (k === e.ENTER) {
103249                     e.stopEvent();
103250                     me.execCmd('InsertHtml','<br /><br />');
103251                     me.deferFocus();
103252                 }
103253             };
103254         }
103255
103256         return null; // not needed, so null
103257     }(),
103258
103259     /**
103260      * Returns the editor's toolbar. <b>This is only available after the editor has been rendered.</b>
103261      * @return {Ext.toolbar.Toolbar}
103262      */
103263     getToolbar : function(){
103264         return this.toolbar;
103265     },
103266
103267     /**
103268      * Object collection of toolbar tooltips for the buttons in the editor. The key
103269      * is the command id associated with that button and the value is a valid QuickTips object.
103270      * For example:
103271 <pre><code>
103272 {
103273     bold : {
103274         title: 'Bold (Ctrl+B)',
103275         text: 'Make the selected text bold.',
103276         cls: 'x-html-editor-tip'
103277     },
103278     italic : {
103279         title: 'Italic (Ctrl+I)',
103280         text: 'Make the selected text italic.',
103281         cls: 'x-html-editor-tip'
103282     },
103283     ...
103284 </code></pre>
103285     * @type Object
103286      */
103287     buttonTips : {
103288         bold : {
103289             title: 'Bold (Ctrl+B)',
103290             text: 'Make the selected text bold.',
103291             cls: Ext.baseCSSPrefix + 'html-editor-tip'
103292         },
103293         italic : {
103294             title: 'Italic (Ctrl+I)',
103295             text: 'Make the selected text italic.',
103296             cls: Ext.baseCSSPrefix + 'html-editor-tip'
103297         },
103298         underline : {
103299             title: 'Underline (Ctrl+U)',
103300             text: 'Underline the selected text.',
103301             cls: Ext.baseCSSPrefix + 'html-editor-tip'
103302         },
103303         increasefontsize : {
103304             title: 'Grow Text',
103305             text: 'Increase the font size.',
103306             cls: Ext.baseCSSPrefix + 'html-editor-tip'
103307         },
103308         decreasefontsize : {
103309             title: 'Shrink Text',
103310             text: 'Decrease the font size.',
103311             cls: Ext.baseCSSPrefix + 'html-editor-tip'
103312         },
103313         backcolor : {
103314             title: 'Text Highlight Color',
103315             text: 'Change the background color of the selected text.',
103316             cls: Ext.baseCSSPrefix + 'html-editor-tip'
103317         },
103318         forecolor : {
103319             title: 'Font Color',
103320             text: 'Change the color of the selected text.',
103321             cls: Ext.baseCSSPrefix + 'html-editor-tip'
103322         },
103323         justifyleft : {
103324             title: 'Align Text Left',
103325             text: 'Align text to the left.',
103326             cls: Ext.baseCSSPrefix + 'html-editor-tip'
103327         },
103328         justifycenter : {
103329             title: 'Center Text',
103330             text: 'Center text in the editor.',
103331             cls: Ext.baseCSSPrefix + 'html-editor-tip'
103332         },
103333         justifyright : {
103334             title: 'Align Text Right',
103335             text: 'Align text to the right.',
103336             cls: Ext.baseCSSPrefix + 'html-editor-tip'
103337         },
103338         insertunorderedlist : {
103339             title: 'Bullet List',
103340             text: 'Start a bulleted list.',
103341             cls: Ext.baseCSSPrefix + 'html-editor-tip'
103342         },
103343         insertorderedlist : {
103344             title: 'Numbered List',
103345             text: 'Start a numbered list.',
103346             cls: Ext.baseCSSPrefix + 'html-editor-tip'
103347         },
103348         createlink : {
103349             title: 'Hyperlink',
103350             text: 'Make the selected text a hyperlink.',
103351             cls: Ext.baseCSSPrefix + 'html-editor-tip'
103352         },
103353         sourceedit : {
103354             title: 'Source Edit',
103355             text: 'Switch to source editing mode.',
103356             cls: Ext.baseCSSPrefix + 'html-editor-tip'
103357         }
103358     }
103359
103360     // hide stuff that is not compatible
103361     /**
103362      * @event blur
103363      * @hide
103364      */
103365     /**
103366      * @event change
103367      * @hide
103368      */
103369     /**
103370      * @event focus
103371      * @hide
103372      */
103373     /**
103374      * @event specialkey
103375      * @hide
103376      */
103377     /**
103378      * @cfg {String} fieldCls @hide
103379      */
103380     /**
103381      * @cfg {String} focusCls @hide
103382      */
103383     /**
103384      * @cfg {String} autoCreate @hide
103385      */
103386     /**
103387      * @cfg {String} inputType @hide
103388      */
103389     /**
103390      * @cfg {String} invalidCls @hide
103391      */
103392     /**
103393      * @cfg {String} invalidText @hide
103394      */
103395     /**
103396      * @cfg {String} msgFx @hide
103397      */
103398     /**
103399      * @cfg {Boolean} allowDomMove  @hide
103400      */
103401     /**
103402      * @cfg {String} applyTo @hide
103403      */
103404     /**
103405      * @cfg {String} readOnly  @hide
103406      */
103407     /**
103408      * @cfg {String} tabIndex  @hide
103409      */
103410     /**
103411      * @method validate
103412      * @hide
103413      */
103414 });
103415
103416 /**
103417  * @class Ext.form.field.Radio
103418  * @extends Ext.form.field.Checkbox
103419
103420 Single radio field. Similar to checkbox, but automatically handles making sure only one radio is checked
103421 at a time within a group of radios with the same name.
103422
103423 __Labeling:__
103424 In addition to the {@link Ext.form.Labelable standard field labeling options}, radio buttons
103425 may be given an optional {@link #boxLabel} which will be displayed immediately to the right of the input. Also
103426 see {@link Ext.form.RadioGroup} for a convenient method of grouping related radio buttons.
103427
103428 __Values:__
103429 The main value of a Radio field is a boolean, indicating whether or not the radio is checked.
103430
103431 The following values will check the radio:
103432 * `true`
103433 * `'true'`
103434 * `'1'`
103435 * `'on'`
103436
103437 Any other value will uncheck it.
103438
103439 In addition to the main boolean value, you may also specify a separate {@link #inputValue}. This will be sent
103440 as the parameter value when the form is {@link Ext.form.Basic#submit submitted}. You will want to set this
103441 value if you have multiple radio buttons with the same {@link #name}, as is almost always the case.
103442 {@img Ext.form.Radio/Ext.form.Radio.png Ext.form.Radio component}
103443 __Example usage:__
103444
103445     Ext.create('Ext.form.Panel', {
103446         title      : 'Order Form',
103447         width      : 300,
103448         bodyPadding: 10,
103449         renderTo   : Ext.getBody(),
103450         items: [
103451             {
103452                 xtype      : 'fieldcontainer',
103453                 fieldLabel : 'Size',
103454                 defaultType: 'radiofield',
103455                 defaults: {
103456                     flex: 1
103457                 },
103458                 layout: 'hbox',
103459                 items: [
103460                     {
103461                         boxLabel  : 'M',
103462                         name      : 'size',
103463                         inputValue: 'm',
103464                         id        : 'radio1'
103465                     }, {
103466                         boxLabel  : 'L',
103467                         name      : 'size',
103468                         inputValue: 'l',
103469                         id        : 'radio2'
103470                     }, {
103471                         boxLabel  : 'XL',
103472                         name      : 'size',
103473                         inputValue: 'xl',
103474                         id        : 'radio3'
103475                     }
103476                 ]
103477             },
103478             {
103479                 xtype      : 'fieldcontainer',
103480                 fieldLabel : 'Color',
103481                 defaultType: 'radiofield',
103482                 defaults: {
103483                     flex: 1
103484                 },
103485                 layout: 'hbox',
103486                 items: [
103487                     {
103488                         boxLabel  : 'Blue',
103489                         name      : 'color',
103490                         inputValue: 'blue',
103491                         id        : 'radio4'
103492                     }, {
103493                         boxLabel  : 'Grey',
103494                         name      : 'color',
103495                         inputValue: 'grey',
103496                         id        : 'radio5'
103497                     }, {
103498                         boxLabel  : 'Black',
103499                         name      : 'color',
103500                         inputValue: 'black',
103501                         id        : 'radio6'
103502                     }
103503                 ]
103504             }
103505         ],
103506         bbar: [
103507             {
103508                 text: 'Smaller Size',
103509                 handler: function() {
103510                     var radio1 = Ext.getCmp('radio1'),
103511                         radio2 = Ext.getCmp('radio2'),
103512                         radio3 = Ext.getCmp('radio3');
103513     
103514                     //if L is selected, change to M
103515                     if (radio2.getValue()) {
103516                         radio1.setValue(true);
103517                         return;
103518                     }
103519     
103520                     //if XL is selected, change to L
103521                     if (radio3.getValue()) {
103522                         radio2.setValue(true);
103523                         return;
103524                     }
103525     
103526                     //if nothing is set, set size to S
103527                     radio1.setValue(true);
103528                 }
103529             },
103530             {
103531                 text: 'Larger Size',
103532                 handler: function() {
103533                     var radio1 = Ext.getCmp('radio1'),
103534                         radio2 = Ext.getCmp('radio2'),
103535                         radio3 = Ext.getCmp('radio3');
103536     
103537                     //if M is selected, change to L
103538                     if (radio1.getValue()) {
103539                         radio2.setValue(true);
103540                         return;
103541                     }
103542     
103543                     //if L is selected, change to XL
103544                     if (radio2.getValue()) {
103545                         radio3.setValue(true);
103546                         return;
103547                     }
103548     
103549                     //if nothing is set, set size to XL
103550                     radio3.setValue(true);
103551                 }
103552             },
103553             '-',
103554             {
103555                 text: 'Select color',
103556                 menu: {
103557                     indent: false,
103558                     items: [
103559                         {
103560                             text: 'Blue',
103561                             handler: function() {
103562                                 var radio = Ext.getCmp('radio4');
103563                                 radio.setValue(true);
103564                             }
103565                         },
103566                         {
103567                             text: 'Grey',
103568                             handler: function() {
103569                                 var radio = Ext.getCmp('radio5');
103570                                 radio.setValue(true);
103571                             }
103572                         },
103573                         {
103574                             text: 'Black',
103575                             handler: function() {
103576                                 var radio = Ext.getCmp('radio6');
103577                                 radio.setValue(true);
103578                             }
103579                         }
103580                     ]
103581                 }
103582             }
103583         ]
103584     });
103585
103586
103587  * @constructor
103588  * Creates a new Radio
103589  * @param {Object} config Configuration options
103590  * @xtype radio
103591  * @docauthor Robert Dougan <rob@sencha.com>
103592  * @markdown
103593  */
103594 Ext.define('Ext.form.field.Radio', {
103595     extend:'Ext.form.field.Checkbox',
103596     alias: ['widget.radiofield', 'widget.radio'],
103597     alternateClassName: 'Ext.form.Radio',
103598     requires: ['Ext.form.RadioManager'],
103599
103600     isRadio: true,
103601
103602     /**
103603      * @cfg {String} uncheckedValue @hide
103604      */
103605
103606     // private
103607     inputType: 'radio',
103608     ariaRole: 'radio',
103609
103610     /**
103611      * If this radio is part of a group, it will return the selected value
103612      * @return {String}
103613      */
103614     getGroupValue: function() {
103615         var selected = this.getManager().getChecked(this.name);
103616         return selected ? selected.inputValue : null;
103617     },
103618
103619     /**
103620      * @private Handle click on the radio button
103621      */
103622     onBoxClick: function(e) {
103623         var me = this;
103624         if (!me.disabled && !me.readOnly) {
103625             this.setValue(true);
103626         }
103627     },
103628
103629     /**
103630      * Sets either the checked/unchecked status of this Radio, or, if a string value
103631      * is passed, checks a sibling Radio of the same name whose value is the value specified.
103632      * @param value {String/Boolean} Checked value, or the value of the sibling radio button to check.
103633      * @return {Ext.form.field.Radio} this
103634      */
103635     setValue: function(v) {
103636         var me = this,
103637             active;
103638
103639         if (Ext.isBoolean(v)) {
103640             me.callParent(arguments);
103641         } else {
103642             active = me.getManager().getWithValue(me.name, v).getAt(0);
103643             if (active) {
103644                 active.setValue(true);
103645             }
103646         }
103647         return me;
103648     },
103649
103650     /**
103651      * Returns the submit value for the checkbox which can be used when submitting forms.
103652      * @return {Boolean/null} True if checked, null if not.
103653      */
103654     getSubmitValue: function() {
103655         return this.checked ? this.inputValue : null;
103656     },
103657
103658     // inherit docs
103659     onChange: function(newVal, oldVal) {
103660         var me = this;
103661         me.callParent(arguments);
103662
103663         if (newVal) {
103664             this.getManager().getByName(me.name).each(function(item){
103665                 if (item !== me) {
103666                     item.setValue(false);
103667                 }
103668             }, me);
103669         }
103670     },
103671
103672     // inherit docs
103673     beforeDestroy: function(){
103674         this.callParent();
103675         this.getManager().removeAtKey(this.id);
103676     },
103677
103678     // inherit docs
103679     getManager: function() {
103680         return Ext.form.RadioManager;
103681     }
103682 });
103683
103684 /**
103685  * @class Ext.picker.Time
103686  * @extends Ext.view.BoundList
103687  * <p>A time picker which provides a list of times from which to choose. This is used by the
103688  * {@link Ext.form.field.Time} class to allow browsing and selection of valid times, but could also be used
103689  * with other components.</p>
103690  * <p>By default, all times starting at midnight and incrementing every 15 minutes will be presented.
103691  * This list of available times can be controlled using the {@link #minValue}, {@link #maxValue}, and
103692  * {@link #increment} configuration properties. The format of the times presented in the list can be
103693  * customized with the {@link #format} config.</p>
103694  * <p>To handle when the user selects a time from the list, you can subscribe to the {@link #selectionchange}
103695  * event.</p>
103696  *
103697  * {@img Ext.picker.Time/Ext.picker.Time.png Ext.picker.Time component}
103698  *
103699  * ## Code
103700      new Ext.create('Ext.picker.Time', {
103701         width: 60,
103702         minValue: Ext.Date.parse('04:30:00 AM', 'h:i:s A'),
103703         maxValue: Ext.Date.parse('08:00:00 AM', 'h:i:s A'),
103704         renderTo: Ext.getBody()
103705     });
103706  *
103707  * @constructor
103708  * Create a new TimePicker
103709  * @param {Object} config The config object
103710  *
103711  * @xtype timepicker
103712  */
103713 Ext.define('Ext.picker.Time', {
103714     extend: 'Ext.view.BoundList',
103715     alias: 'widget.timepicker',
103716     requires: ['Ext.data.Store', 'Ext.Date'],
103717
103718     /**
103719      * @cfg {Date} minValue
103720      * The minimum time to be shown in the list of times. This must be a Date object (only the time fields
103721      * will be used); no parsing of String values will be done. Defaults to undefined.
103722      */
103723
103724     /**
103725      * @cfg {Date} maxValue
103726      * The maximum time to be shown in the list of times. This must be a Date object (only the time fields
103727      * will be used); no parsing of String values will be done. Defaults to undefined.
103728      */
103729
103730     /**
103731      * @cfg {Number} increment
103732      * The number of minutes between each time value in the list (defaults to 15).
103733      */
103734     increment: 15,
103735
103736     /**
103737      * @cfg {String} format
103738      * The default time format string which can be overriden for localization support. The format must be
103739      * valid according to {@link Ext.Date#parse} (defaults to 'g:i A', e.g., '3:15 PM'). For 24-hour time
103740      * format try 'H:i' instead.
103741      */
103742     format : "g:i A",
103743
103744     /**
103745      * @hide
103746      * The field in the implicitly-generated Model objects that gets displayed in the list. This is
103747      * an internal field name only and is not useful to change via config.
103748      */
103749     displayField: 'disp',
103750
103751     /**
103752      * @private
103753      * Year, month, and day that all times will be normalized into internally.
103754      */
103755     initDate: [2008,1,1],
103756
103757     componentCls: Ext.baseCSSPrefix + 'timepicker',
103758
103759     /**
103760      * @hide
103761      */
103762     loadingText: '',
103763
103764     initComponent: function() {
103765         var me = this,
103766             dateUtil = Ext.Date,
103767             clearTime = dateUtil.clearTime,
103768             initDate = me.initDate.join('/');
103769
103770         // Set up absolute min and max for the entire day
103771         me.absMin = clearTime(new Date(initDate));
103772         me.absMax = dateUtil.add(clearTime(new Date(initDate)), 'mi', (24 * 60) - 1);
103773
103774         me.store = me.createStore();
103775         me.updateList();
103776
103777         this.callParent();
103778     },
103779
103780     /**
103781      * Set the {@link #minValue} and update the list of available times. This must be a Date
103782      * object (only the time fields will be used); no parsing of String values will be done.
103783      * @param {Date} value
103784      */
103785     setMinValue: function(value) {
103786         this.minValue = value;
103787         this.updateList();
103788     },
103789
103790     /**
103791      * Set the {@link #maxValue} and update the list of available times. This must be a Date
103792      * object (only the time fields will be used); no parsing of String values will be done.
103793      * @param {Date} value
103794      */
103795     setMaxValue: function(value) {
103796         this.maxValue = value;
103797         this.updateList();
103798     },
103799
103800     /**
103801      * @private
103802      * Sets the year/month/day of the given Date object to the {@link #initDate}, so that only
103803      * the time fields are significant. This makes values suitable for time comparison.
103804      * @param {Date} date
103805      */
103806     normalizeDate: function(date) {
103807         var initDate = this.initDate;
103808         date.setFullYear(initDate[0], initDate[1] - 1, initDate[2]);
103809         return date;
103810     },
103811
103812     /**
103813      * Update the list of available times in the list to be constrained within the
103814      * {@link #minValue} and {@link #maxValue}.
103815      */
103816     updateList: function() {
103817         var me = this,
103818             min = me.normalizeDate(me.minValue || me.absMin),
103819             max = me.normalizeDate(me.maxValue || me.absMax);
103820
103821         me.store.filterBy(function(record) {
103822             var date = record.get('date');
103823             return date >= min && date <= max;
103824         });
103825     },
103826
103827     /**
103828      * @private
103829      * Creates the internal {@link Ext.data.Store} that contains the available times. The store
103830      * is loaded with all possible times, and it is later filtered to hide those times outside
103831      * the minValue/maxValue.
103832      */
103833     createStore: function() {
103834         var me = this,
103835             utilDate = Ext.Date,
103836             times = [],
103837             min = me.absMin,
103838             max = me.absMax;
103839
103840         while(min <= max){
103841             times.push({
103842                 disp: utilDate.dateFormat(min, me.format),
103843                 date: min
103844             });
103845             min = utilDate.add(min, 'mi', me.increment);
103846         }
103847
103848         return Ext.create('Ext.data.Store', {
103849             fields: ['disp', 'date'],
103850             data: times
103851         });
103852     }
103853
103854 });
103855
103856 /**
103857  * @class Ext.form.field.Time
103858  * @extends Ext.form.field.Picker
103859  * <p>Provides a time input field with a time dropdown and automatic time validation.</p>
103860  * <p>This field recognizes and uses JavaScript Date objects as its main {@link #value} type (only the time
103861  * portion of the date is used; the month/day/year are ignored). In addition, it recognizes string values which
103862  * are parsed according to the {@link #format} and/or {@link #altFormats} configs. These may be reconfigured
103863  * to use time formats appropriate for the user's locale.</p>
103864  * <p>The field may be limited to a certain range of times by using the {@link #minValue} and {@link #maxValue}
103865  * configs, and the interval between time options in the dropdown can be changed with the {@link #increment} config.</p>
103866  * {@img Ext.form.Time/Ext.form.Time.png Ext.form.Time component}
103867  * <p>Example usage:</p>
103868  * <pre><code>
103869     Ext.create('Ext.form.Panel', {
103870         title: 'Time Card',
103871         width: 300,
103872         bodyPadding: 10,
103873         renderTo: Ext.getBody(),        
103874         items: [{
103875             xtype: 'timefield',
103876             name: 'in',
103877             fieldLabel: 'Time In',
103878             minValue: '6:00 AM',
103879             maxValue: '8:00 PM',
103880             increment: 30,
103881             anchor: '100%'
103882         }, {
103883             xtype: 'timefield',
103884             name: 'out',
103885             fieldLabel: 'Time Out',
103886             minValue: '6:00 AM',
103887             maxValue: '8:00 PM',
103888             increment: 30,
103889             anchor: '100%'
103890        }]
103891     });
103892 </code></pre>
103893  * @constructor
103894  * Create a new Time field
103895  * @param {Object} config
103896  * @xtype timefield
103897  */
103898 Ext.define('Ext.form.field.Time', {
103899     extend:'Ext.form.field.Picker',
103900     alias: 'widget.timefield',
103901     requires: ['Ext.form.field.Date', 'Ext.picker.Time', 'Ext.view.BoundListKeyNav', 'Ext.Date'],
103902     alternateClassName: ['Ext.form.TimeField', 'Ext.form.Time'],
103903
103904     /**
103905      * @cfg {String} triggerCls
103906      * An additional CSS class used to style the trigger button.  The trigger will always get the
103907      * {@link #triggerBaseCls} by default and <tt>triggerCls</tt> will be <b>appended</b> if specified.
103908      * Defaults to <tt>'x-form-time-trigger'</tt> for the Time field trigger.
103909      */
103910     triggerCls: Ext.baseCSSPrefix + 'form-time-trigger',
103911
103912     /**
103913      * @cfg {Date/String} minValue
103914      * The minimum allowed time. Can be either a Javascript date object with a valid time value or a string
103915      * time in a valid format -- see {@link #format} and {@link #altFormats} (defaults to undefined).
103916      */
103917
103918     /**
103919      * @cfg {Date/String} maxValue
103920      * The maximum allowed time. Can be either a Javascript date object with a valid time value or a string
103921      * time in a valid format -- see {@link #format} and {@link #altFormats} (defaults to undefined).
103922      */
103923
103924     /**
103925      * @cfg {String} minText
103926      * The error text to display when the entered time is before {@link #minValue} (defaults to
103927      * 'The time in this field must be equal to or after {0}').
103928      */
103929     minText : "The time in this field must be equal to or after {0}",
103930
103931     /**
103932      * @cfg {String} maxText
103933      * The error text to display when the entered time is after {@link #maxValue} (defaults to
103934      * 'The time in this field must be equal to or before {0}').
103935      */
103936     maxText : "The time in this field must be equal to or before {0}",
103937
103938     /**
103939      * @cfg {String} invalidText
103940      * The error text to display when the time in the field is invalid (defaults to
103941      * '{value} is not a valid time').
103942      */
103943     invalidText : "{0} is not a valid time",
103944
103945     /**
103946      * @cfg {String} format
103947      * The default time format string which can be overriden for localization support.  The format must be
103948      * valid according to {@link Ext.Date#parse} (defaults to 'g:i A', e.g., '3:15 PM').  For 24-hour time
103949      * format try 'H:i' instead.
103950      */
103951     format : "g:i A",
103952
103953     /**
103954      * @cfg {String} submitFormat The date format string which will be submitted to the server.
103955      * The format must be valid according to {@link Ext.Date#parse} (defaults to <tt>{@link #format}</tt>).
103956      */
103957
103958     /**
103959      * @cfg {String} altFormats
103960      * Multiple date formats separated by "|" to try when parsing a user input value and it doesn't match the defined
103961      * 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').
103962      */
103963     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",
103964
103965     /**
103966      * @cfg {Number} increment
103967      * The number of minutes between each time value in the list (defaults to 15).
103968      */
103969     increment: 15,
103970
103971     /**
103972      * @cfg {Number} pickerMaxHeight
103973      * The maximum height of the {@link Ext.picker.Time} dropdown. Defaults to 300.
103974      */
103975     pickerMaxHeight: 300,
103976
103977     /**
103978      * @cfg {Boolean} selectOnTab
103979      * Whether the Tab key should select the currently highlighted item. Defaults to <tt>true</tt>.
103980      */
103981     selectOnTab: true,
103982
103983     /**
103984      * @private
103985      * This is the date to use when generating time values in the absence of either minValue
103986      * or maxValue.  Using the current date causes DST issues on DST boundary dates, so this is an
103987      * arbitrary "safe" date that can be any date aside from DST boundary dates.
103988      */
103989     initDate: '1/1/2008',
103990     initDateFormat: 'j/n/Y',
103991
103992
103993     initComponent: function() {
103994         var me = this,
103995             min = me.minValue,
103996             max = me.maxValue;
103997         if (min) {
103998             me.setMinValue(min);
103999         }
104000         if (max) {
104001             me.setMaxValue(max);
104002         }
104003         this.callParent();
104004     },
104005
104006     initValue: function() {
104007         var me = this,
104008             value = me.value;
104009
104010         // If a String value was supplied, try to convert it to a proper Date object
104011         if (Ext.isString(value)) {
104012             me.value = me.rawToValue(value);
104013         }
104014
104015         me.callParent();
104016     },
104017
104018     /**
104019      * Replaces any existing {@link #minValue} with the new time and refreshes the picker's range.
104020      * @param {Date/String} value The minimum time that can be selected
104021      */
104022     setMinValue: function(value) {
104023         var me = this,
104024             picker = me.picker;
104025         me.setLimit(value, true);
104026         if (picker) {
104027             picker.setMinValue(me.minValue);
104028         }
104029     },
104030
104031     /**
104032      * Replaces any existing {@link #maxValue} with the new time and refreshes the picker's range.
104033      * @param {Date/String} value The maximum time that can be selected
104034      */
104035     setMaxValue: function(value) {
104036         var me = this,
104037             picker = me.picker;
104038         me.setLimit(value, false);
104039         if (picker) {
104040             picker.setMaxValue(me.maxValue);
104041         }
104042     },
104043
104044     /**
104045      * @private
104046      * Updates either the min or max value. Converts the user's value into a Date object whose
104047      * year/month/day is set to the {@link #initDate} so that only the time fields are significant.
104048      */
104049     setLimit: function(value, isMin) {
104050         var me = this,
104051             d, val;
104052         if (Ext.isString(value)) {
104053             d = me.parseDate(value);
104054         }
104055         else if (Ext.isDate(value)) {
104056             d = value;
104057         }
104058         if (d) {
104059             val = Ext.Date.clearTime(new Date(me.initDate));
104060             val.setHours(d.getHours(), d.getMinutes(), d.getSeconds(), d.getMilliseconds());
104061             me[isMin ? 'minValue' : 'maxValue'] = val;
104062         }
104063     },
104064
104065     rawToValue: function(rawValue) {
104066         return this.parseDate(rawValue) || rawValue || null;
104067     },
104068
104069     valueToRaw: function(value) {
104070         return this.formatDate(this.parseDate(value));
104071     },
104072
104073     /**
104074      * Runs all of Time's validations and returns an array of any errors. Note that this first
104075      * runs Text's validations, so the returned array is an amalgamation of all field errors.
104076      * The additional validation checks are testing that the time format is valid, that the chosen
104077      * time is within the {@link #minValue} and {@link #maxValue} constraints set.
104078      * @param {Mixed} value The value to get errors for (defaults to the current field value)
104079      * @return {Array} All validation errors for this field
104080      */
104081     getErrors: function(value) {
104082         var me = this,
104083             format = Ext.String.format,
104084             errors = me.callParent(arguments),
104085             minValue = me.minValue,
104086             maxValue = me.maxValue,
104087             date;
104088
104089         value = me.formatDate(value || me.processRawValue(me.getRawValue()));
104090
104091         if (value === null || value.length < 1) { // if it's blank and textfield didn't flag it then it's valid
104092              return errors;
104093         }
104094
104095         date = me.parseDate(value);
104096         if (!date) {
104097             errors.push(format(me.invalidText, value, me.format));
104098             return errors;
104099         }
104100
104101         if (minValue && date < minValue) {
104102             errors.push(format(me.minText, me.formatDate(minValue)));
104103         }
104104
104105         if (maxValue && date > maxValue) {
104106             errors.push(format(me.maxText, me.formatDate(maxValue)));
104107         }
104108
104109         return errors;
104110     },
104111
104112     formatDate: function() {
104113         return Ext.form.field.Date.prototype.formatDate.apply(this, arguments);
104114     },
104115
104116     /**
104117      * @private
104118      * Parses an input value into a valid Date object.
104119      * @param {String/Date} value
104120      */
104121     parseDate: function(value) {
104122         if (!value || Ext.isDate(value)) {
104123             return value;
104124         }
104125
104126         var me = this,
104127             val = me.safeParse(value, me.format),
104128             altFormats = me.altFormats,
104129             altFormatsArray = me.altFormatsArray,
104130             i = 0,
104131             len;
104132
104133         if (!val && altFormats) {
104134             altFormatsArray = altFormatsArray || altFormats.split('|');
104135             len = altFormatsArray.length;
104136             for (; i < len && !val; ++i) {
104137                 val = me.safeParse(value, altFormatsArray[i]);
104138             }
104139         }
104140         return val;
104141     },
104142
104143     safeParse: function(value, format){
104144         var me = this,
104145             utilDate = Ext.Date,
104146             parsedDate,
104147             result = null;
104148
104149         if (utilDate.formatContainsDateInfo(format)) {
104150             // assume we've been given a full date
104151             result = utilDate.parse(value, format);
104152         } else {
104153             // Use our initial safe date
104154             parsedDate = utilDate.parse(me.initDate + ' ' + value, me.initDateFormat + ' ' + format);
104155             if (parsedDate) {
104156                 result = parsedDate;
104157             }
104158         }
104159         return result;
104160     },
104161
104162     // @private
104163     getSubmitValue: function() {
104164         var me = this,
104165             format = me.submitFormat || me.format,
104166             value = me.getValue();
104167
104168         return value ? Ext.Date.format(value, format) : null;
104169     },
104170
104171     /**
104172      * @private
104173      * Creates the {@link Ext.picker.Time}
104174      */
104175     createPicker: function() {
104176         var me = this,
104177             picker = Ext.create('Ext.picker.Time', {
104178                 selModel: {
104179                     mode: 'SINGLE'
104180                 },
104181                 floating: true,
104182                 hidden: true,
104183                 minValue: me.minValue,
104184                 maxValue: me.maxValue,
104185                 increment: me.increment,
104186                 format: me.format,
104187                 ownerCt: this.ownerCt,
104188                 renderTo: document.body,
104189                 maxHeight: me.pickerMaxHeight,
104190                 focusOnToFront: false
104191             });
104192
104193         me.mon(picker.getSelectionModel(), {
104194             selectionchange: me.onListSelect,
104195             scope: me
104196         });
104197
104198         return picker;
104199     },
104200
104201     /**
104202      * @private
104203      * Enables the key nav for the Time picker when it is expanded.
104204      * TODO this is largely the same logic as ComboBox, should factor out.
104205      */
104206     onExpand: function() {
104207         var me = this,
104208             keyNav = me.pickerKeyNav,
104209             selectOnTab = me.selectOnTab,
104210             picker = me.getPicker(),
104211             lastSelected = picker.getSelectionModel().lastSelected,
104212             itemNode;
104213
104214         if (!keyNav) {
104215             keyNav = me.pickerKeyNav = Ext.create('Ext.view.BoundListKeyNav', this.inputEl, {
104216                 boundList: picker,
104217                 forceKeyDown: true,
104218                 tab: function(e) {
104219                     if (selectOnTab) {
104220                         this.selectHighlighted(e);
104221                         me.triggerBlur();
104222                     }
104223                     // Tab key event is allowed to propagate to field
104224                     return true;
104225                 }
104226             });
104227             // stop tab monitoring from Ext.form.field.Trigger so it doesn't short-circuit selectOnTab
104228             if (selectOnTab) {
104229                 me.ignoreMonitorTab = true;
104230             }
104231         }
104232         Ext.defer(keyNav.enable, 1, keyNav); //wait a bit so it doesn't react to the down arrow opening the picker
104233
104234         // Highlight the last selected item and scroll it into view
104235         if (lastSelected) {
104236             itemNode = picker.getNode(lastSelected);
104237             if (itemNode) {
104238                 picker.highlightItem(itemNode);
104239                 picker.el.scrollChildIntoView(itemNode, false);
104240             }
104241         }
104242     },
104243
104244     /**
104245      * @private
104246      * Disables the key nav for the Time picker when it is collapsed.
104247      */
104248     onCollapse: function() {
104249         var me = this,
104250             keyNav = me.pickerKeyNav;
104251         if (keyNav) {
104252             keyNav.disable();
104253             me.ignoreMonitorTab = false;
104254         }
104255     },
104256
104257     /**
104258      * @private
104259      * Handles a time being selected from the Time picker.
104260      */
104261     onListSelect: function(list, recordArray) {
104262         var me = this,
104263             record = recordArray[0],
104264             val = record ? record.get('date') : null;
104265         me.setValue(val);
104266         me.fireEvent('select', me, val);
104267         me.picker.clearHighlight();
104268         me.collapse();
104269         me.inputEl.focus();
104270     }
104271 });
104272
104273
104274 /**
104275  * @class Ext.grid.CellEditor
104276  * @extends Ext.Editor
104277  * Internal utility class that provides default configuration for cell editing.
104278  * @ignore
104279  */
104280 Ext.define('Ext.grid.CellEditor', {
104281     extend: 'Ext.Editor',
104282     constructor: function(config) {
104283         if (config.field) {
104284             config.field.monitorTab = false;
104285         }
104286         config.autoSize = {
104287             width: 'boundEl'
104288         };
104289         this.callParent(arguments);
104290     },
104291     
104292     /**
104293      * @private
104294      * Hide the grid cell when editor is shown.
104295      */
104296     onShow: function() {
104297         var first = this.boundEl.first();
104298         if (first) {
104299             first.hide();
104300         }
104301         this.callParent(arguments);
104302     },
104303     
104304     /**
104305      * @private
104306      * Show grid cell when editor is hidden.
104307      */
104308     onHide: function() {
104309         var first = this.boundEl.first();
104310         if (first) {
104311             first.show();
104312         }
104313         this.callParent(arguments);
104314     },
104315     
104316     /**
104317      * @private
104318      * Fix checkbox blur when it is clicked.
104319      */
104320     afterRender: function() {
104321         this.callParent(arguments);
104322         var field = this.field;
104323         if (field.isXType('checkboxfield')) {
104324             field.mon(field.inputEl, 'mousedown', this.onCheckBoxMouseDown, this);
104325             field.mon(field.inputEl, 'click', this.onCheckBoxClick, this);
104326         }
104327     },
104328     
104329     /**
104330      * @private
104331      * Because when checkbox is clicked it loses focus  completeEdit is bypassed.
104332      */
104333     onCheckBoxMouseDown: function() {
104334         this.completeEdit = Ext.emptyFn;
104335     },
104336     
104337     /**
104338      * @private
104339      * Restore checkbox focus and completeEdit method.
104340      */
104341     onCheckBoxClick: function() {
104342         delete this.completeEdit;
104343         this.field.focus(false, 10);
104344     },
104345     
104346     alignment: "tl-tl",
104347     hideEl : false,
104348     cls: Ext.baseCSSPrefix + "small-editor " + Ext.baseCSSPrefix + "grid-editor",
104349     shim: false,
104350     shadow: false
104351 });
104352 /**
104353  * @class Ext.grid.ColumnLayout
104354  * @extends Ext.layout.container.HBox
104355  * @private
104356  *
104357  * <p>This class is used only by the grid's HeaderContainer docked child.</p>
104358  *
104359  * <p>This class adds the ability to shrink the vertical size of the inner container element back if a grouped
104360  * column header has all its child columns dragged out, and the whole HeaderContainer needs to shrink back down.</p>
104361  *
104362  * <p>It also enforces the grid's HeaderContainer's forceFit config by, after every calaculateChildBoxes call, converting
104363  * all pixel widths into flex values, so that propertions are maintained upon width change of the grid.</p>
104364  *
104365  * <p>Also, after every layout, after all headers have attained their 'stretchmax' height, it goes through and calls
104366  * <code>setPadding</code> on the columns so that they lay out correctly. TODO: implement a ColumnHeader component
104367  * layout which takes responsibility for this, and will run upon resize.</p>
104368  */
104369 Ext.define('Ext.grid.ColumnLayout', {
104370     extend: 'Ext.layout.container.HBox',
104371     alias: 'layout.gridcolumn',
104372     type : 'column',
104373
104374     // Height-stretched innerCt must be able to revert back to unstretched height
104375     clearInnerCtOnLayout: false,
104376
104377     constructor: function() {
104378         var me = this;
104379         me.callParent(arguments);
104380         if (!Ext.isDefined(me.availableSpaceOffset)) {
104381             me.availableSpaceOffset = (Ext.getScrollBarWidth() - 2);
104382         }
104383     },
104384
104385     beforeLayout: function() {
104386         var me = this,
104387             i = 0,
104388             items = me.getLayoutItems(),
104389             len = items.length,
104390             item, returnValue;
104391
104392         returnValue = me.callParent(arguments);
104393
104394         // Size to a sane minimum height before possibly being stretched to accommodate grouped headers
104395         me.innerCt.setHeight(23);
104396
104397         // Unstretch child items before the layout which stretches them.
104398         if (me.align == 'stretchmax') {
104399             for (; i < len; i++) {
104400                 item = items[i];
104401                 item.el.setStyle({
104402                     height: 'auto'
104403                 });
104404                 item.titleContainer.setStyle({
104405                     height: 'auto',
104406                     paddingTop: '0'
104407                 });
104408                 if (item.componentLayout && item.componentLayout.lastComponentSize) {
104409                     item.componentLayout.lastComponentSize.height = item.el.dom.offsetHeight;
104410                 }
104411             }
104412         }
104413         return returnValue;
104414     },
104415
104416     // Override to enforce the forceFit config.
104417     calculateChildBoxes: function(visibleItems, targetSize) {
104418         var me = this,
104419             calculations = me.callParent(arguments),
104420             boxes = calculations.boxes,
104421             metaData = calculations.meta,
104422             len = boxes.length, i = 0, box, item;
104423
104424         if (targetSize.width && !me.isColumn) {
104425             // If configured forceFit then all columns will be flexed
104426             if (me.owner.forceFit) {
104427
104428                 for (; i < len; i++) {
104429                     box = boxes[i];
104430                     item = box.component;
104431
104432                     // Set a sane minWidth for the Box layout to be able to squeeze flexed Headers down to.
104433                     item.minWidth = Ext.grid.plugin.HeaderResizer.prototype.minColWidth;
104434
104435                     // For forceFit, just use allocated width as the flex value, and the proportions
104436                     // will end up the same whatever HeaderContainer width they are being forced into.
104437                     item.flex = box.width;
104438                 }
104439
104440                 // Recalculate based upon all columns now being flexed instead of sized.
104441                 calculations = me.callParent(arguments);
104442             }
104443             else if (metaData.tooNarrow) {
104444                 targetSize.width = metaData.desiredSize;
104445             }
104446         }
104447
104448         return calculations;
104449     },
104450
104451     afterLayout: function() {
104452         var me = this,
104453             i = 0,
104454             items = me.getLayoutItems(),
104455             len = items.length;
104456
104457         me.callParent(arguments);
104458
104459         // Set up padding in items
104460         if (me.align == 'stretchmax') {
104461             for (; i < len; i++) {
104462                 items[i].setPadding();
104463             }
104464         }
104465     },
104466
104467     // FIX: when flexing we actually don't have enough space as we would
104468     // typically because of the scrollOffset on the GridView, must reserve this
104469     updateInnerCtSize: function(tSize, calcs) {
104470         var me    = this,
104471             extra = 0;
104472
104473         // Columns must not account for scroll offset
104474         if (!me.isColumn && calcs.meta.tooNarrow) {
104475             if (
104476                 Ext.isWebKit ||
104477                 Ext.isGecko ||
104478                 (Ext.isIEQuirks && (Ext.isIE6 || Ext.isIE7 || Ext.isIE8))
104479             ) {
104480                 extra = 1;
104481             // IE6-8 not quirks
104482             } else if (Ext.isIE6 || Ext.isIE7 || Ext.isIE8) {
104483                 extra = 2;
104484             }
104485             
104486             // this is the 1px accounted for in the Scroller when subtracting 1px.
104487             extra++;
104488             tSize.width = calcs.meta.desiredSize + (me.reserveOffset ? me.availableSpaceOffset : 0) + extra;
104489         }
104490         return me.callParent(arguments);
104491     },
104492
104493     doOwnerCtLayouts: function() {
104494         var ownerCt = this.owner.ownerCt;
104495         if (!ownerCt.componentLayout.layoutBusy) {
104496             ownerCt.doComponentLayout();
104497         }
104498     }
104499 });
104500 /**
104501  * @class Ext.grid.LockingView
104502  * This class is used internally to provide a single interface when using
104503  * a locking grid. Internally, the locking grid creates 2 separate grids,
104504  * so this class is used to map calls appropriately.
104505  * @ignore
104506  */
104507 Ext.define('Ext.grid.LockingView', {
104508     
104509     mixins: {
104510         observable: 'Ext.util.Observable'
104511     },
104512     
104513     eventRelayRe: /^(beforeitem|beforecontainer|item|container|cell)/,
104514     
104515     constructor: function(config){
104516         var me = this,
104517             eventNames = [],
104518             eventRe = me.eventRelayRe,
104519             locked = config.locked.getView(),
104520             normal = config.normal.getView(),
104521             events,
104522             event;
104523         
104524         Ext.apply(me, {
104525             lockedView: locked,
104526             normalView: normal,
104527             lockedGrid: config.locked,
104528             normalGrid: config.normal,
104529             panel: config.panel
104530         });
104531         me.mixins.observable.constructor.call(me, config);
104532         
104533         // relay events
104534         events = locked.events;
104535         for (event in events) {
104536             if (events.hasOwnProperty(event) && eventRe.test(event)) {
104537                 eventNames.push(event);
104538             }
104539         }
104540         me.relayEvents(locked, eventNames);
104541         me.relayEvents(normal, eventNames);
104542         
104543         normal.on({
104544             scope: me,
104545             itemmouseleave: me.onItemMouseLeave,
104546             itemmouseenter: me.onItemMouseEnter
104547         });
104548         
104549         locked.on({
104550             scope: me,
104551             itemmouseleave: me.onItemMouseLeave,
104552             itemmouseenter: me.onItemMouseEnter
104553         });
104554     },
104555     
104556     getGridColumns: function() {
104557         var cols = this.lockedGrid.headerCt.getGridColumns();
104558         return cols.concat(this.normalGrid.headerCt.getGridColumns());
104559     },
104560     
104561     onItemMouseEnter: function(view, record){
104562         var me = this,
104563             locked = me.lockedView,
104564             other = me.normalView,
104565             item;
104566             
104567         if (view.trackOver) {
104568             if (view !== locked) {
104569                 other = locked;
104570             }
104571             item = other.getNode(record);
104572             other.highlightItem(item);
104573         }
104574     },
104575     
104576     onItemMouseLeave: function(view, record){
104577         var me = this,
104578             locked = me.lockedView,
104579             other = me.normalView;
104580             
104581         if (view.trackOver) {
104582             if (view !== locked) {
104583                 other = locked;
104584             }
104585             other.clearHighlight();
104586         }
104587     },
104588     
104589     relayFn: function(name, args){
104590         args = args || [];
104591         
104592         var view = this.lockedView;
104593         view[name].apply(view, args || []);    
104594         view = this.normalView;
104595         view[name].apply(view, args || []);   
104596     },
104597     
104598     getSelectionModel: function(){
104599         return this.panel.getSelectionModel();    
104600     },
104601     
104602     getStore: function(){
104603         return this.panel.store;
104604     },
104605     
104606     getNode: function(nodeInfo){
104607         // default to the normal view
104608         return this.normalView.getNode(nodeInfo);
104609     },
104610     
104611     getCell: function(record, column){
104612         var view = this.lockedView,
104613             row;
104614         
104615         
104616         if (view.getHeaderAtIndex(column) === -1) {
104617             view = this.normalView;
104618         }
104619         
104620         row = view.getNode(record);
104621         return Ext.fly(row).down(column.getCellSelector());
104622     },
104623     
104624     getRecord: function(node){
104625         var result = this.lockedView.getRecord(node);
104626         if (!node) {
104627             result = this.normalView.getRecord(node);
104628         }
104629         return result;
104630     },
104631     
104632     refreshNode: function(){
104633         this.relayFn('refreshNode', arguments);
104634     },
104635     
104636     refresh: function(){
104637         this.relayFn('refresh', arguments);
104638     },
104639     
104640     bindStore: function(){
104641         this.relayFn('bindStore', arguments);
104642     },
104643     
104644     addRowCls: function(){
104645         this.relayFn('addRowCls', arguments);
104646     },
104647     
104648     removeRowCls: function(){
104649         this.relayFn('removeRowCls', arguments);
104650     }
104651        
104652 });
104653 /**
104654  * @class Ext.grid.Lockable
104655  * @private
104656  *
104657  * Lockable is a private mixin which injects lockable behavior into any
104658  * TablePanel subclass such as GridPanel or TreePanel. TablePanel will
104659  * automatically inject the Ext.grid.Lockable mixin in when one of the
104660  * these conditions are met:
104661  * - The TablePanel has the lockable configuration set to true
104662  * - One of the columns in the TablePanel has locked set to true/false
104663  *
104664  * Each TablePanel subclass *must* register an alias. It should have an array
104665  * of configurations to copy to the 2 separate tablepanel's that will be generated
104666  * to note what configurations should be copied. These are named normalCfgCopy and
104667  * lockedCfgCopy respectively.
104668  *
104669  * Columns which are locked must specify a fixed width. They do *NOT* support a
104670  * flex width.
104671  *
104672  * Configurations which are specified in this class will be available on any grid or
104673  * tree which is using the lockable functionality.
104674  */
104675 Ext.define('Ext.grid.Lockable', {
104676     
104677     requires: ['Ext.grid.LockingView'],
104678     
104679     /**
104680      * @cfg {Boolean} syncRowHeight Synchronize rowHeight between the normal and
104681      * locked grid view. This is turned on by default. If your grid is guaranteed
104682      * to have rows of all the same height, you should set this to false to
104683      * optimize performance.
104684      */
104685     syncRowHeight: true,
104686     
104687     /**
104688      * @cfg {String} subGridXType The xtype of the subgrid to specify. If this is
104689      * not specified lockable will determine the subgrid xtype to create by the
104690      * following rule. Use the superclasses xtype if the superclass is NOT
104691      * tablepanel, otherwise use the xtype itself.
104692      */
104693     
104694     /**
104695      * @cfg {Object} lockedViewConfig A view configuration to be applied to the
104696      * locked side of the grid. Any conflicting configurations between lockedViewConfig
104697      * and viewConfig will be overwritten by the lockedViewConfig.
104698      */
104699
104700     /**
104701      * @cfg {Object} normalViewConfig A view configuration to be applied to the
104702      * normal/unlocked side of the grid. Any conflicting configurations between normalViewConfig
104703      * and viewConfig will be overwritten by the normalViewConfig.
104704      */
104705     
104706     // private variable to track whether or not the spacer is hidden/visible
104707     spacerHidden: true,
104708     
104709     // i8n text
104710     unlockText: 'Unlock',
104711     lockText: 'Lock',
104712     
104713     determineXTypeToCreate: function() {
104714         var me = this,
104715             typeToCreate;
104716
104717         if (me.subGridXType) {
104718             typeToCreate = me.subGridXType;
104719         } else {
104720             var xtypes     = this.getXTypes().split('/'),
104721                 xtypesLn   = xtypes.length,
104722                 xtype      = xtypes[xtypesLn - 1],
104723                 superxtype = xtypes[xtypesLn - 2];
104724                 
104725             if (superxtype !== 'tablepanel') {
104726                 typeToCreate = superxtype;
104727             } else {
104728                 typeToCreate = xtype;
104729             }
104730         }
104731         
104732         return typeToCreate;
104733     },
104734     
104735     // injectLockable will be invoked before initComponent's parent class implementation
104736     // is called, so throughout this method this. are configurations
104737     injectLockable: function() {
104738         // ensure lockable is set to true in the TablePanel
104739         this.lockable = true;
104740         // Instruct the TablePanel it already has a view and not to create one.
104741         // We are going to aggregate 2 copies of whatever TablePanel we are using
104742         this.hasView = true;
104743
104744         var me = this,
104745             // xtype of this class, 'treepanel' or 'gridpanel'
104746             // (Note: this makes it a requirement that any subclass that wants to use lockable functionality needs to register an
104747             // alias.)
104748             xtype = me.determineXTypeToCreate(),
104749             // share the selection model
104750             selModel = me.getSelectionModel(),
104751             lockedGrid = {
104752                 xtype: xtype,
104753                 // Lockable does NOT support animations for Tree
104754                 enableAnimations: false,
104755                 scroll: false,
104756                 scrollerOwner: false,
104757                 selModel: selModel,
104758                 border: false,
104759                 cls: Ext.baseCSSPrefix + 'grid-inner-locked'
104760             },
104761             normalGrid = {
104762                 xtype: xtype,
104763                 enableAnimations: false,
104764                 scrollerOwner: false,
104765                 selModel: selModel,
104766                 border: false
104767             },
104768             i = 0,
104769             columns,
104770             lockedHeaderCt,
104771             normalHeaderCt;
104772         
104773         me.addCls(Ext.baseCSSPrefix + 'grid-locked');
104774         
104775         // copy appropriate configurations to the respective
104776         // aggregated tablepanel instances and then delete them
104777         // from the master tablepanel.
104778         Ext.copyTo(normalGrid, me, me.normalCfgCopy);
104779         Ext.copyTo(lockedGrid, me, me.lockedCfgCopy);
104780         for (; i < me.normalCfgCopy.length; i++) {
104781             delete me[me.normalCfgCopy[i]];
104782         }
104783         for (i = 0; i < me.lockedCfgCopy.length; i++) {
104784             delete me[me.lockedCfgCopy[i]];
104785         }
104786         
104787         me.lockedHeights = [];
104788         me.normalHeights = [];
104789         
104790         columns = me.processColumns(me.columns);
104791
104792         lockedGrid.width = columns.lockedWidth;
104793         lockedGrid.columns = columns.locked;
104794         normalGrid.columns = columns.normal;
104795         
104796         me.store = Ext.StoreManager.lookup(me.store);
104797         lockedGrid.store = me.store;
104798         normalGrid.store = me.store;
104799         
104800         // normal grid should flex the rest of the width
104801         normalGrid.flex = 1;
104802         lockedGrid.viewConfig = me.lockedViewConfig || {};
104803         lockedGrid.viewConfig.loadingUseMsg = false;
104804         normalGrid.viewConfig = me.normalViewConfig || {};
104805         
104806         Ext.applyIf(lockedGrid.viewConfig, me.viewConfig);
104807         Ext.applyIf(normalGrid.viewConfig, me.viewConfig);
104808         
104809         me.normalGrid = Ext.ComponentManager.create(normalGrid);
104810         me.lockedGrid = Ext.ComponentManager.create(lockedGrid);
104811         
104812         me.view = Ext.create('Ext.grid.LockingView', {
104813             locked: me.lockedGrid,
104814             normal: me.normalGrid,
104815             panel: me    
104816         });
104817         
104818         if (me.syncRowHeight) {
104819             me.lockedGrid.getView().on({
104820                 refresh: me.onLockedGridAfterRefresh,
104821                 itemupdate: me.onLockedGridAfterUpdate,
104822                 scope: me
104823             });
104824             
104825             me.normalGrid.getView().on({
104826                 refresh: me.onNormalGridAfterRefresh,
104827                 itemupdate: me.onNormalGridAfterUpdate,
104828                 scope: me
104829             });
104830         }
104831         
104832         lockedHeaderCt = me.lockedGrid.headerCt;
104833         normalHeaderCt = me.normalGrid.headerCt;
104834         
104835         lockedHeaderCt.lockedCt = true;
104836         lockedHeaderCt.lockableInjected = true;
104837         normalHeaderCt.lockableInjected = true;
104838         
104839         lockedHeaderCt.on({
104840             columnshow: me.onLockedHeaderShow,
104841             columnhide: me.onLockedHeaderHide,
104842             columnmove: me.onLockedHeaderMove,
104843             sortchange: me.onLockedHeaderSortChange,
104844             columnresize: me.onLockedHeaderResize,
104845             scope: me
104846         });
104847         
104848         normalHeaderCt.on({
104849             columnmove: me.onNormalHeaderMove,
104850             sortchange: me.onNormalHeaderSortChange,
104851             scope: me
104852         });
104853         
104854         me.normalGrid.on({
104855             scrollershow: me.onScrollerShow,
104856             scrollerhide: me.onScrollerHide,
104857             scope: me
104858         });
104859         
104860         me.lockedGrid.on('afterlayout', me.onLockedGridAfterLayout, me, {single: true});
104861         
104862         me.modifyHeaderCt();
104863         me.items = [me.lockedGrid, me.normalGrid];
104864
104865         me.layout = {
104866             type: 'hbox',
104867             align: 'stretch'
104868         };
104869     },
104870     
104871     processColumns: function(columns){
104872         // split apart normal and lockedWidths
104873         var i = 0,
104874             len = columns.length,
104875             lockedWidth = 0,
104876             lockedHeaders = [],
104877             normalHeaders = [],
104878             column;
104879             
104880         for (; i < len; ++i) {
104881             column = columns[i];
104882             // mark the column as processed so that the locked attribute does not
104883             // trigger trying to aggregate the columns again.
104884             column.processed = true;
104885             if (column.locked) {
104886                 if (column.flex) {
104887                     Ext.Error.raise("Columns which are locked do NOT support a flex width. You must set a width on the " + columns[i].text + "column.");
104888                 }
104889                 lockedWidth += column.width;
104890                 lockedHeaders.push(column);
104891             } else {
104892                 normalHeaders.push(column);
104893             }
104894         }
104895         return {
104896             lockedWidth: lockedWidth,
104897             locked: lockedHeaders,
104898             normal: normalHeaders    
104899         };
104900     },
104901     
104902     // create a new spacer after the table is refreshed
104903     onLockedGridAfterLayout: function() {
104904         var me         = this,
104905             lockedView = me.lockedGrid.getView();
104906         lockedView.on({
104907             refresh: me.createSpacer,
104908             beforerefresh: me.destroySpacer,
104909             scope: me
104910         });
104911     },
104912     
104913     // trigger a pseudo refresh on the normal side
104914     onLockedHeaderMove: function() {
104915         if (this.syncRowHeight) {
104916             this.onNormalGridAfterRefresh();
104917         }
104918     },
104919     
104920     // trigger a pseudo refresh on the locked side
104921     onNormalHeaderMove: function() {
104922         if (this.syncRowHeight) {
104923             this.onLockedGridAfterRefresh();
104924         }
104925     },
104926     
104927     // create a spacer in lockedsection and store a reference
104928     // TODO: Should destroy before refreshing content
104929     createSpacer: function() {
104930         var me   = this,
104931             // This affects scrolling all the way to the bottom of a locked grid
104932             // additional test, sort a column and make sure it synchronizes
104933             w    = Ext.getScrollBarWidth() + (Ext.isIE ? 2 : 0),
104934             view = me.lockedGrid.getView(),
104935             el   = view.el;
104936
104937         me.spacerEl = Ext.core.DomHelper.append(el, {
104938             cls: me.spacerHidden ? (Ext.baseCSSPrefix + 'hidden') : '',
104939             style: 'height: ' + w + 'px;'
104940         }, true);
104941     },
104942     
104943     destroySpacer: function() {
104944         var me = this;
104945         if (me.spacerEl) {
104946             me.spacerEl.destroy();
104947             delete me.spacerEl;
104948         }
104949     },
104950     
104951     // cache the heights of all locked rows and sync rowheights
104952     onLockedGridAfterRefresh: function() {
104953         var me     = this,
104954             view   = me.lockedGrid.getView(),
104955             el     = view.el,
104956             rowEls = el.query(view.getItemSelector()),
104957             ln     = rowEls.length,
104958             i = 0;
104959             
104960         // reset heights each time.
104961         me.lockedHeights = [];
104962         
104963         for (; i < ln; i++) {
104964             me.lockedHeights[i] = rowEls[i].clientHeight;
104965         }
104966         me.syncRowHeights();
104967     },
104968     
104969     // cache the heights of all normal rows and sync rowheights
104970     onNormalGridAfterRefresh: function() {
104971         var me     = this,
104972             view   = me.normalGrid.getView(),
104973             el     = view.el,
104974             rowEls = el.query(view.getItemSelector()),
104975             ln     = rowEls.length,
104976             i = 0;
104977             
104978         // reset heights each time.
104979         me.normalHeights = [];
104980         
104981         for (; i < ln; i++) {
104982             me.normalHeights[i] = rowEls[i].clientHeight;
104983         }
104984         me.syncRowHeights();
104985     },
104986     
104987     // rows can get bigger/smaller
104988     onLockedGridAfterUpdate: function(record, index, node) {
104989         this.lockedHeights[index] = node.clientHeight;
104990         this.syncRowHeights();
104991     },
104992     
104993     // rows can get bigger/smaller
104994     onNormalGridAfterUpdate: function(record, index, node) {
104995         this.normalHeights[index] = node.clientHeight;
104996         this.syncRowHeights();
104997     },
104998     
104999     // match the rowheights to the biggest rowheight on either
105000     // side
105001     syncRowHeights: function() {
105002         var me = this,
105003             lockedHeights = me.lockedHeights,
105004             normalHeights = me.normalHeights,
105005             calcHeights   = [],
105006             ln = lockedHeights.length,
105007             i  = 0,
105008             lockedView, normalView,
105009             lockedRowEls, normalRowEls,
105010             vertScroller = me.getVerticalScroller(),
105011             scrollTop;
105012
105013         // ensure there are an equal num of locked and normal
105014         // rows before synchronization
105015         if (lockedHeights.length && normalHeights.length) {
105016             lockedView = me.lockedGrid.getView();
105017             normalView = me.normalGrid.getView();
105018             lockedRowEls = lockedView.el.query(lockedView.getItemSelector());
105019             normalRowEls = normalView.el.query(normalView.getItemSelector());
105020
105021             // loop thru all of the heights and sync to the other side
105022             for (; i < ln; i++) {
105023                 // ensure both are numbers
105024                 if (!isNaN(lockedHeights[i]) && !isNaN(normalHeights[i])) {
105025                     if (lockedHeights[i] > normalHeights[i]) {
105026                         Ext.fly(normalRowEls[i]).setHeight(lockedHeights[i]);
105027                     } else if (lockedHeights[i] < normalHeights[i]) {
105028                         Ext.fly(lockedRowEls[i]).setHeight(normalHeights[i]);
105029                     }
105030                 }
105031             }
105032
105033             // invalidate the scroller and sync the scrollers
105034             me.normalGrid.invalidateScroller();
105035             
105036             // synchronize the view with the scroller, if we have a virtualScrollTop
105037             // then the user is using a PagingScroller 
105038             if (vertScroller && vertScroller.setViewScrollTop) {
105039                 vertScroller.setViewScrollTop(me.virtualScrollTop);
105040             } else {
105041                 // We don't use setScrollTop here because if the scrollTop is
105042                 // set to the exact same value some browsers won't fire the scroll
105043                 // event. Instead, we directly set the scrollTop.
105044                 scrollTop = normalView.el.dom.scrollTop;
105045                 normalView.el.dom.scrollTop = scrollTop;
105046                 lockedView.el.dom.scrollTop = scrollTop;
105047             }
105048             
105049             // reset the heights
105050             me.lockedHeights = [];
105051             me.normalHeights = [];
105052         }
105053     },
105054     
105055     // track when scroller is shown
105056     onScrollerShow: function(scroller, direction) {
105057         if (direction === 'horizontal') {
105058             this.spacerHidden = false;
105059             this.spacerEl.removeCls(Ext.baseCSSPrefix + 'hidden');
105060         }
105061     },
105062     
105063     // track when scroller is hidden
105064     onScrollerHide: function(scroller, direction) {
105065         if (direction === 'horizontal') {
105066             this.spacerHidden = true;
105067             this.spacerEl.addCls(Ext.baseCSSPrefix + 'hidden');
105068         }
105069     },
105070
105071     
105072     // inject Lock and Unlock text
105073     modifyHeaderCt: function() {
105074         var me = this;
105075         me.lockedGrid.headerCt.getMenuItems = me.getMenuItems(true);
105076         me.normalGrid.headerCt.getMenuItems = me.getMenuItems(false);
105077     },
105078     
105079     onUnlockMenuClick: function() {
105080         this.unlock();
105081     },
105082     
105083     onLockMenuClick: function() {
105084         this.lock();
105085     },
105086     
105087     getMenuItems: function(locked) {
105088         var me            = this,
105089             unlockText    = me.unlockText,
105090             lockText      = me.lockText,
105091             // TODO: Refactor to use Ext.baseCSSPrefix
105092             unlockCls     = 'xg-hmenu-unlock',
105093             lockCls       = 'xg-hmenu-lock',
105094             unlockHandler = Ext.Function.bind(me.onUnlockMenuClick, me),
105095             lockHandler   = Ext.Function.bind(me.onLockMenuClick, me);
105096         
105097         // runs in the scope of headerCt
105098         return function() {
105099             var o = Ext.grid.header.Container.prototype.getMenuItems.call(this);
105100             o.push('-',{
105101                 cls: unlockCls,
105102                 text: unlockText,
105103                 handler: unlockHandler,
105104                 disabled: !locked
105105             });
105106             o.push({
105107                 cls: lockCls,
105108                 text: lockText,
105109                 handler: lockHandler,
105110                 disabled: locked
105111             });
105112             return o;
105113         };
105114     },
105115     
105116     // going from unlocked section to locked
105117     /**
105118      * Locks the activeHeader as determined by which menu is open OR a header
105119      * as specified.
105120      * @param {Ext.grid.column.Column} header (Optional) Header to unlock from the locked section. Defaults to the header which has the menu open currently.
105121      * @param {Number} toIdx (Optional) The index to move the unlocked header to. Defaults to appending as the last item.
105122      * @private
105123      */
105124     lock: function(activeHd, toIdx) {
105125         var me         = this,
105126             normalGrid = me.normalGrid,
105127             lockedGrid = me.lockedGrid,
105128             normalHCt  = normalGrid.headerCt,
105129             lockedHCt  = lockedGrid.headerCt;
105130             
105131         activeHd = activeHd || normalHCt.getMenu().activeHeader;
105132         
105133         // if column was previously flexed, get/set current width
105134         // and remove the flex
105135         if (activeHd.flex) {
105136             activeHd.width = activeHd.getWidth();
105137             delete activeHd.flex;
105138         }
105139         
105140         normalHCt.remove(activeHd, false);
105141         lockedHCt.suspendLayout = true;
105142         if (Ext.isDefined(toIdx)) {
105143             lockedHCt.insert(toIdx, activeHd);
105144         } else {
105145             lockedHCt.add(activeHd);
105146         }
105147         lockedHCt.suspendLayout = false;
105148         me.syncLockedSection();
105149     },
105150     
105151     syncLockedSection: function() {
105152         var me = this;
105153         me.syncLockedWidth();
105154         me.lockedGrid.getView().refresh();
105155         me.normalGrid.getView().refresh();
105156     },
105157     
105158     // adjust the locked section to the width of its respective
105159     // headerCt
105160     syncLockedWidth: function() {
105161         var me = this,
105162             width = me.lockedGrid.headerCt.getFullWidth(true);
105163         me.lockedGrid.setWidth(width);
105164     },
105165     
105166     onLockedHeaderResize: function() {
105167         this.syncLockedWidth();
105168     },
105169     
105170     onLockedHeaderHide: function() {
105171         this.syncLockedWidth();
105172     },
105173     
105174     onLockedHeaderShow: function() {
105175         this.syncLockedWidth();
105176     },
105177     
105178     onLockedHeaderSortChange: function(headerCt, header, sortState) {
105179         if (sortState) {
105180             // no real header, and silence the event so we dont get into an
105181             // infinite loop
105182             this.normalGrid.headerCt.clearOtherSortStates(null, true);
105183         }
105184     },
105185     
105186     onNormalHeaderSortChange: function(headerCt, header, sortState) {
105187         if (sortState) {
105188             // no real header, and silence the event so we dont get into an
105189             // infinite loop
105190             this.lockedGrid.headerCt.clearOtherSortStates(null, true);
105191         }
105192     },
105193     
105194     // going from locked section to unlocked
105195     /**
105196      * Unlocks the activeHeader as determined by which menu is open OR a header
105197      * as specified.
105198      * @param {Ext.grid.column.Column} header (Optional) Header to unlock from the locked section. Defaults to the header which has the menu open currently.
105199      * @param {Number} toIdx (Optional) The index to move the unlocked header to. Defaults to 0.
105200      * @private
105201      */
105202     unlock: function(activeHd, toIdx) {
105203         var me         = this,
105204             normalGrid = me.normalGrid,
105205             lockedGrid = me.lockedGrid,
105206             normalHCt  = normalGrid.headerCt,
105207             lockedHCt  = lockedGrid.headerCt;
105208
105209         if (!Ext.isDefined(toIdx)) {
105210             toIdx = 0;
105211         }
105212         activeHd = activeHd || lockedHCt.getMenu().activeHeader;
105213         
105214         lockedHCt.remove(activeHd, false);
105215         me.syncLockedWidth();
105216         me.lockedGrid.getView().refresh();
105217         normalHCt.insert(toIdx, activeHd);
105218         me.normalGrid.getView().refresh();
105219     },
105220     
105221     // we want to totally override the reconfigure behaviour here, since we're creating 2 sub-grids
105222     reconfigureLockable: function(store, columns) {
105223         var me = this,
105224             lockedGrid = me.lockedGrid,
105225             normalGrid = me.normalGrid;
105226         
105227         if (columns) {
105228             lockedGrid.headerCt.removeAll();
105229             normalGrid.headerCt.removeAll();
105230             
105231             columns = me.processColumns(columns);
105232             lockedGrid.setWidth(columns.lockedWidth);
105233             lockedGrid.headerCt.add(columns.locked);
105234             normalGrid.headerCt.add(columns.normal);
105235         }
105236         
105237         if (store) {
105238             store = Ext.data.StoreManager.lookup(store);
105239             me.store = store;
105240             lockedGrid.bindStore(store);
105241             normalGrid.bindStore(store);
105242         } else {
105243             lockedGrid.getView().refresh();
105244             normalGrid.getView().refresh();
105245         }
105246     }
105247 });
105248
105249 /**
105250  * @class Ext.grid.Scroller
105251  * @extends Ext.Component
105252  *
105253  * Docked in an Ext.grid.Panel, controls virtualized scrolling and synchronization
105254  * across different sections.
105255  *
105256  * @private
105257  */
105258 Ext.define('Ext.grid.Scroller', {
105259     extend: 'Ext.Component',
105260     alias: 'widget.gridscroller',
105261     weight: 110,
105262     cls: Ext.baseCSSPrefix + 'scroller',
105263     focusable: false,
105264     
105265     renderTpl: ['<div class="' + Ext.baseCSSPrefix + 'stretcher"></div>'],
105266     
105267     initComponent: function() {
105268         var me       = this,
105269             dock     = me.dock,
105270             cls      = Ext.baseCSSPrefix + 'scroller-vertical',
105271             sizeProp = 'width',
105272             // Subtracting 2px would give us a perfect fit of the scroller
105273             // however, some browsers wont allow us to scroll content thats not
105274             // visible, therefore we use 1px.
105275             // Note: This 1px offset matches code in Ext.grid.ColumnLayout when
105276             // reserving room for the scrollbar
105277             scrollbarWidth = Ext.getScrollBarWidth() + (Ext.isIE ? 1 : -1);
105278
105279         me.offsets = {bottom: 0};
105280
105281         if (dock === 'top' || dock === 'bottom') {
105282             cls = Ext.baseCSSPrefix + 'scroller-horizontal';
105283             sizeProp = 'height';
105284         }
105285         me[sizeProp] = scrollbarWidth;
105286         
105287         me.cls += (' ' + cls);
105288         
105289         Ext.applyIf(me.renderSelectors, {
105290             stretchEl: '.' + Ext.baseCSSPrefix + 'stretcher'
105291         });
105292         me.callParent();
105293     },
105294     
105295     
105296     afterRender: function() {
105297         var me = this;
105298         me.callParent();
105299         me.ownerCt.on('afterlayout', me.onOwnerAfterLayout, me);
105300         me.mon(me.el, 'scroll', me.onElScroll, me);
105301         Ext.cache[me.el.id].skipGarbageCollection = true;
105302     },
105303     
105304     getSizeCalculation: function() {
105305         var owner  = this.getPanel(),
105306             dock   = this.dock,
105307             elDom  = this.el.dom,
105308             width  = 1,
105309             height = 1,
105310             view, tbl;
105311             
105312         if (dock === 'top' || dock === 'bottom') {
105313             // TODO: Must gravitate to a single region..
105314             // Horizontal scrolling only scrolls virtualized region
105315             var items  = owner.query('tableview'),
105316                 center = items[1] || items[0];
105317             
105318             if (!center) {
105319                 return false;
105320             }
105321             // center is not guaranteed to have content, such as when there
105322             // are zero rows in the grid/tree. We read the width from the
105323             // headerCt instead.
105324             width = center.headerCt.getFullWidth();
105325             
105326             if (Ext.isIEQuirks) {
105327                 width--;
105328             }
105329             // Account for the 1px removed in Scroller.
105330             width--;
105331         } else {            
105332             view = owner.down('tableview:not([lockableInjected])');
105333             if (!view) {
105334                 return false;
105335             }
105336             tbl = view.el;
105337             if (!tbl) {
105338                 return false;
105339             }
105340             
105341             // needs to also account for header and scroller (if still in picture)
105342             // should calculate from headerCt.
105343             height = tbl.dom.scrollHeight;
105344         }
105345         if (isNaN(width)) {
105346             width = 1;
105347         }
105348         if (isNaN(height)) {
105349             height = 1;
105350         }
105351         return {
105352             width: width,
105353             height: height
105354         };
105355     },
105356     
105357     invalidate: function(firstPass) {
105358         if (!this.stretchEl || !this.ownerCt) {
105359             return;
105360         }
105361         var size  = this.getSizeCalculation(),
105362             elDom = this.el.dom;
105363         if (size) {
105364             this.stretchEl.setSize(size);
105365         
105366             // BrowserBug: IE7
105367             // This makes the scroller enabled, when initially rendering.
105368             elDom.scrollTop = elDom.scrollTop;
105369         }
105370     },
105371
105372     onOwnerAfterLayout: function(owner, layout) {
105373         this.invalidate();
105374     },
105375
105376     /**
105377      * Sets the scrollTop and constrains the value between 0 and max.
105378      * @param {Number} scrollTop
105379      * @return {Number} The resulting scrollTop value after being constrained
105380      */
105381     setScrollTop: function(scrollTop) {
105382         if (this.el) {
105383             var elDom = this.el.dom;
105384             return elDom.scrollTop = Ext.Number.constrain(scrollTop, 0, elDom.scrollHeight - elDom.clientHeight);
105385         }
105386     },
105387
105388     /**
105389      * Sets the scrollLeft and constrains the value between 0 and max.
105390      * @param {Number} scrollLeft
105391      * @return {Number} The resulting scrollLeft value after being constrained
105392      */
105393     setScrollLeft: function(scrollLeft) {
105394         if (this.el) {
105395             var elDom = this.el.dom;
105396             return elDom.scrollLeft = Ext.Number.constrain(scrollLeft, 0, elDom.scrollWidth - elDom.clientWidth);
105397         }
105398     },
105399
105400     /**
105401      * Scroll by deltaY
105402      * @param {Number} delta
105403      * @return {Number} The resulting scrollTop value
105404      */
105405     scrollByDeltaY: function(delta) {
105406         if (this.el) {
105407             var elDom = this.el.dom;
105408             return this.setScrollTop(elDom.scrollTop + delta);
105409         }
105410     },
105411
105412     /**
105413      * Scroll by deltaX
105414      * @param {Number} delta
105415      * @return {Number} The resulting scrollLeft value
105416      */
105417     scrollByDeltaX: function(delta) {
105418         if (this.el) {
105419             var elDom = this.el.dom;
105420             return this.setScrollLeft(elDom.scrollLeft + delta);
105421         }
105422     },
105423     
105424     
105425     /**
105426      * Scroll to the top.
105427      */
105428     scrollToTop : function(){
105429         this.setScrollTop(0);
105430     },
105431     
105432     // synchronize the scroller with the bound gridviews
105433     onElScroll: function(event, target) {
105434         this.fireEvent('bodyscroll', event, target);
105435     },
105436
105437     getPanel: function() {
105438         var me = this;
105439         if (!me.panel) {
105440             me.panel = this.up('[scrollerOwner]');
105441         }
105442         return me.panel;
105443     }
105444 });
105445
105446
105447 /**
105448  * @class Ext.grid.PagingScroller
105449  * @extends Ext.grid.Scroller
105450  *
105451  * @private
105452  */
105453 Ext.define('Ext.grid.PagingScroller', {
105454     extend: 'Ext.grid.Scroller',
105455     alias: 'widget.paginggridscroller',
105456     //renderTpl: null,
105457     //tpl: [
105458     //    '<tpl for="pages">',
105459     //        '<div class="' + Ext.baseCSSPrefix + 'stretcher" style="width: {width}px;height: {height}px;"></div>',
105460     //    '</tpl>'
105461     //],
105462     /**
105463      * @cfg {Number} percentageFromEdge This is a number above 0 and less than 1 which specifies
105464      * at what percentage to begin fetching the next page. For example if the pageSize is 100
105465      * and the percentageFromEdge is the default of 0.35, the paging scroller will prefetch pages
105466      * when scrolling up between records 0 and 34 and when scrolling down between records 65 and 99.
105467      */
105468     percentageFromEdge: 0.35,
105469     
105470     /**
105471      * @cfg {Number} scrollToLoadBuffer This is the time in milliseconds to buffer load requests
105472      * when scrolling the PagingScrollbar.
105473      */
105474     scrollToLoadBuffer: 200,
105475     
105476     activePrefetch: true,
105477     
105478     chunkSize: 50,
105479     snapIncrement: 25,
105480     
105481     syncScroll: true,
105482     
105483     initComponent: function() {
105484         var me = this,
105485             ds = me.store;
105486
105487         ds.on('guaranteedrange', this.onGuaranteedRange, this);
105488         this.callParent(arguments);
105489     },
105490     
105491     
105492     onGuaranteedRange: function(range, start, end) {
105493         var me = this,
105494             ds = me.store,
105495             rs;
105496         // this should never happen
105497         if (range.length && me.visibleStart < range[0].index) {
105498             return;
105499         }
105500         
105501         ds.loadRecords(range);
105502
105503         if (!me.firstLoad) {
105504             if (me.rendered) {
105505                 me.invalidate();
105506             } else {
105507                 me.on('afterrender', this.invalidate, this, {single: true});
105508             }
105509             me.firstLoad = true;
105510         } else {
105511             // adjust to visible
105512             me.syncTo();
105513         }
105514     },
105515     
105516     syncTo: function() {
105517         var me            = this,
105518             pnl           = me.getPanel(),
105519             store         = pnl.store,
105520             scrollerElDom = this.el.dom,
105521             rowOffset     = me.visibleStart - store.guaranteedStart,
105522             scrollBy      = rowOffset * me.rowHeight,
105523             scrollHeight  = scrollerElDom.scrollHeight,
105524             clientHeight  = scrollerElDom.clientHeight,
105525             scrollTop     = scrollerElDom.scrollTop,
105526             useMaximum;
105527         
105528         // BrowserBug: clientHeight reports 0 in IE9 StrictMode
105529         // Instead we are using offsetHeight and hardcoding borders
105530         if (Ext.isIE9 && Ext.isStrict) {
105531             clientHeight = scrollerElDom.offsetHeight + 2;
105532         }
105533
105534         // This should always be zero or greater than zero but staying
105535         // safe and less than 0 we'll scroll to the bottom.        
105536         useMaximum = (scrollHeight - clientHeight - scrollTop <= 0);
105537         this.setViewScrollTop(scrollBy, useMaximum);
105538     },
105539     
105540     getPageData : function(){
105541         var panel = this.getPanel(),
105542             store = panel.store,
105543             totalCount = store.getTotalCount();
105544             
105545         return {
105546             total : totalCount,
105547             currentPage : store.currentPage,
105548             pageCount: Math.ceil(totalCount / store.pageSize),
105549             fromRecord: ((store.currentPage - 1) * store.pageSize) + 1,
105550             toRecord: Math.min(store.currentPage * store.pageSize, totalCount)
105551         };
105552     },
105553     
105554     onElScroll: function(e, t) {
105555         var me = this,
105556             panel = me.getPanel(),
105557             store = panel.store,
105558             pageSize = store.pageSize,
105559             guaranteedStart = store.guaranteedStart,
105560             guaranteedEnd = store.guaranteedEnd,
105561             totalCount = store.getTotalCount(),
105562             numFromEdge = Math.ceil(me.percentageFromEdge * store.pageSize),
105563             position = t.scrollTop,
105564             visibleStart = Math.floor(position / me.rowHeight),
105565             view = panel.down('tableview'),
105566             viewEl = view.el,
105567             visibleHeight = viewEl.getHeight(),
105568             visibleAhead = Math.ceil(visibleHeight / me.rowHeight),
105569             visibleEnd = visibleStart + visibleAhead,
105570             prevPage = Math.floor(visibleStart / store.pageSize),
105571             nextPage = Math.floor(visibleEnd / store.pageSize) + 2,
105572             lastPage = Math.ceil(totalCount / store.pageSize),
105573             //requestStart = visibleStart,
105574             requestStart = Math.floor(visibleStart / me.snapIncrement) * me.snapIncrement,
105575             requestEnd = requestStart + pageSize - 1,
105576             activePrefetch = me.activePrefetch;
105577
105578         me.visibleStart = visibleStart;
105579         me.visibleEnd = visibleEnd;
105580         
105581         
105582         me.syncScroll = true;
105583         if (totalCount >= pageSize) {
105584             // end of request was past what the total is, grab from the end back a pageSize
105585             if (requestEnd > totalCount - 1) {
105586                 this.cancelLoad();
105587                 if (store.rangeSatisfied(totalCount - pageSize, totalCount - 1)) {
105588                     me.syncScroll = true;
105589                 }
105590                 store.guaranteeRange(totalCount - pageSize, totalCount - 1);
105591             // Out of range, need to reset the current data set
105592             } else if (visibleStart < guaranteedStart || visibleEnd > guaranteedEnd) {
105593                 if (store.rangeSatisfied(requestStart, requestEnd)) {
105594                     this.cancelLoad();
105595                     store.guaranteeRange(requestStart, requestEnd);
105596                 } else {
105597                     store.mask();
105598                     me.attemptLoad(requestStart, requestEnd);
105599                 }
105600                 // dont sync the scroll view immediately, sync after the range has been guaranteed
105601                 me.syncScroll = false;
105602             } else if (activePrefetch && visibleStart < (guaranteedStart + numFromEdge) && prevPage > 0) {
105603                 me.syncScroll = true;
105604                 store.prefetchPage(prevPage);
105605             } else if (activePrefetch && visibleEnd > (guaranteedEnd - numFromEdge) && nextPage < lastPage) {
105606                 me.syncScroll = true;
105607                 store.prefetchPage(nextPage);
105608             }
105609         }
105610     
105611     
105612         if (me.syncScroll) {
105613             me.syncTo();
105614         }
105615     },
105616     
105617     getSizeCalculation: function() {
105618         // Use the direct ownerCt here rather than the scrollerOwner
105619         // because we are calculating widths/heights.
105620         var owner = this.ownerCt,
105621             view   = owner.getView(),
105622             store  = this.store,
105623             dock   = this.dock,
105624             elDom  = this.el.dom,
105625             width  = 1,
105626             height = 1;
105627         
105628         if (!this.rowHeight) {
105629             this.rowHeight = view.el.down(view.getItemSelector()).getHeight(false, true);
105630         }
105631
105632         height = store.getTotalCount() * this.rowHeight;
105633
105634         if (isNaN(width)) {
105635             width = 1;
105636         }
105637         if (isNaN(height)) {
105638             height = 1;
105639         }
105640         return {
105641             width: width,
105642             height: height
105643         };
105644     },
105645     
105646     attemptLoad: function(start, end) {
105647         var me = this;
105648         if (!me.loadTask) {
105649             me.loadTask = Ext.create('Ext.util.DelayedTask', me.doAttemptLoad, me, []);
105650         }
105651         me.loadTask.delay(me.scrollToLoadBuffer, me.doAttemptLoad, me, [start, end]);
105652     },
105653     
105654     cancelLoad: function() {
105655         if (this.loadTask) {
105656             this.loadTask.cancel();
105657         }
105658     },
105659     
105660     doAttemptLoad:  function(start, end) {
105661         var store = this.getPanel().store;
105662         store.guaranteeRange(start, end);
105663     },
105664     
105665     setViewScrollTop: function(scrollTop, useMax) {
105666         var owner = this.getPanel(),
105667             items = owner.query('tableview'),
105668             i = 0,
105669             len = items.length,
105670             center,
105671             centerEl,
105672             calcScrollTop,
105673             maxScrollTop,
105674             scrollerElDom = this.el.dom;
105675             
105676         owner.virtualScrollTop = scrollTop;
105677             
105678         center = items[1] || items[0];
105679         centerEl = center.el.dom;
105680         
105681         maxScrollTop = ((owner.store.pageSize * this.rowHeight) - centerEl.clientHeight);
105682         calcScrollTop = (scrollTop % ((owner.store.pageSize * this.rowHeight) + 1));
105683         if (useMax) {
105684             calcScrollTop = maxScrollTop;
105685         }
105686         if (calcScrollTop > maxScrollTop) {
105687             //Ext.Error.raise("Calculated scrollTop was larger than maxScrollTop");
105688             return;
105689             // calcScrollTop = maxScrollTop;
105690         }
105691         for (; i < len; i++) {
105692             items[i].el.dom.scrollTop = calcScrollTop;
105693         }
105694     }
105695 });
105696
105697
105698 /**
105699  * @class Ext.panel.Table
105700  * @extends Ext.panel.Panel
105701  * @xtype tablepanel
105702  * @private
105703  * @author Nicolas Ferrero
105704  * TablePanel is a private class and the basis of both TreePanel and GridPanel.
105705  *
105706  * TablePanel aggregates:
105707  *
105708  *  - a Selection Model
105709  *  - a View
105710  *  - a Store
105711  *  - Scrollers
105712  *  - Ext.grid.header.Container
105713  *
105714  */
105715 Ext.define('Ext.panel.Table', {
105716     extend: 'Ext.panel.Panel',
105717
105718     alias: 'widget.tablepanel',
105719
105720     uses: [
105721         'Ext.selection.RowModel',
105722         'Ext.grid.Scroller',
105723         'Ext.grid.header.Container',
105724         'Ext.grid.Lockable'
105725     ],
105726
105727     cls: Ext.baseCSSPrefix + 'grid',
105728     extraBodyCls: Ext.baseCSSPrefix + 'grid-body',
105729
105730     layout: 'fit',
105731     /**
105732      * Boolean to indicate that a view has been injected into the panel.
105733      * @property hasView
105734      */
105735     hasView: false,
105736
105737     // each panel should dictate what viewType and selType to use
105738     viewType: null,
105739     selType: 'rowmodel',
105740
105741     /**
105742      * @cfg {Number} scrollDelta
105743      * Number of pixels to scroll when scrolling with mousewheel.
105744      * Defaults to 40.
105745      */
105746     scrollDelta: 40,
105747
105748     /**
105749      * @cfg {String/Boolean} scroll
105750      * Valid values are 'both', 'horizontal' or 'vertical'. true implies 'both'. false implies 'none'.
105751      * Defaults to true.
105752      */
105753     scroll: true,
105754
105755     /**
105756      * @cfg {Array} columns
105757      * An array of {@link Ext.grid.column.Column column} definition objects which define all columns that appear in this grid. Each
105758      * column definition provides the header text for the column, and a definition of where the data for that column comes from.
105759      */
105760
105761     /**
105762      * @cfg {Boolean} forceFit
105763      * 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
105764      * a specific width, or flex. Then they are all proportionally changed in width so that the entire content width is used..
105765      */
105766
105767     /**
105768      * @cfg {Boolean} hideHeaders
105769      * Specify as <code>true</code> to hide the headers.
105770      */
105771
105772     /**
105773      * @cfg {Boolean} sortableColumns
105774      * Defaults to true. Set to false to disable column sorting via clicking the
105775      * header and via the Sorting menu items.
105776      */
105777     sortableColumns: true,
105778
105779     verticalScrollDock: 'right',
105780     verticalScrollerType: 'gridscroller',
105781
105782     horizontalScrollerPresentCls: Ext.baseCSSPrefix + 'horizontal-scroller-present',
105783     verticalScrollerPresentCls: Ext.baseCSSPrefix + 'vertical-scroller-present',
105784
105785     // private property used to determine where to go down to find views
105786     // this is here to support locking.
105787     scrollerOwner: true,
105788
105789     invalidateScrollerOnRefresh: true,
105790     
105791     enableColumnMove: true,
105792     enableColumnResize: true,
105793
105794
105795     initComponent: function() {
105796         if (!this.viewType) {
105797             Ext.Error.raise("You must specify a viewType config.");
105798         }
105799         if (!this.store) {
105800             Ext.Error.raise("You must specify a store config");
105801         }
105802         if (this.headers) {
105803             Ext.Error.raise("The headers config is not supported. Please specify columns instead.");
105804         }
105805
105806         var me          = this,
105807             scroll      = me.scroll,
105808             vertical    = false,
105809             horizontal  = false,
105810             headerCtCfg = me.columns || me.colModel,
105811             i           = 0,
105812             view,
105813             border = me.border;
105814
105815         // Set our determinScrollbars method to reference a buffered call to determinScrollbars which fires on a 30ms buffer.
105816         me.determineScrollbars = Ext.Function.createBuffered(me.determineScrollbars, 30);
105817         me.injectView = Ext.Function.createBuffered(me.injectView, 30);
105818
105819         if (me.hideHeaders) {
105820             border = false;
105821         }
105822
105823         // The columns/colModel config may be either a fully instantiated HeaderContainer, or an array of Column definitions, or a config object of a HeaderContainer
105824         // Either way, we extract a columns property referencing an array of Column definitions.
105825         if (headerCtCfg instanceof Ext.grid.header.Container) {
105826             me.headerCt = headerCtCfg;
105827             me.headerCt.border = border;
105828             me.columns = me.headerCt.items.items;
105829         } else {
105830             if (Ext.isArray(headerCtCfg)) {
105831                 headerCtCfg = {
105832                     items: headerCtCfg,
105833                     border: border
105834                 };
105835             }
105836             Ext.apply(headerCtCfg, {
105837                 forceFit: me.forceFit,
105838                 sortable: me.sortableColumns,
105839                 enableColumnMove: me.enableColumnMove,
105840                 enableColumnResize: me.enableColumnResize,
105841                 border:  border
105842             });
105843             me.columns = headerCtCfg.items;
105844
105845              // If any of the Column objects contain a locked property, and are not processed, this is a lockable TablePanel, a
105846              // special view will be injected by the Ext.grid.Lockable mixin, so no processing of .
105847              if (Ext.ComponentQuery.query('{locked !== undefined}{processed != true}', me.columns).length) {
105848                  me.self.mixin('lockable', Ext.grid.Lockable);
105849                  me.injectLockable();
105850              }
105851         }
105852
105853         me.store = Ext.data.StoreManager.lookup(me.store);
105854         me.addEvents(
105855             /**
105856              * @event scrollerhide
105857              * Fires when a scroller is hidden
105858              * @param {Ext.grid.Scroller} scroller
105859              * @param {String} orientation Orientation, can be 'vertical' or 'horizontal'
105860              */
105861             'scrollerhide',
105862             /**
105863              * @event scrollershow
105864              * Fires when a scroller is shown
105865              * @param {Ext.grid.Scroller} scroller
105866              * @param {String} orientation Orientation, can be 'vertical' or 'horizontal'
105867              */
105868             'scrollershow'
105869         );
105870
105871         me.bodyCls = me.bodyCls || '';
105872         me.bodyCls += (' ' + me.extraBodyCls);
105873
105874         // autoScroll is not a valid configuration
105875         delete me.autoScroll;
105876
105877         // If this TablePanel is lockable (Either configured lockable, or any of the defined columns has a 'locked' property)
105878         // 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.
105879         if (!me.hasView) {
105880
105881             // If we were not configured with a ready-made headerCt (either by direct config with a headerCt property, or by passing
105882             // a HeaderContainer instance as the 'columns' property, then go ahead and create one from the config object created above.
105883             if (!me.headerCt) {
105884                 me.headerCt = Ext.create('Ext.grid.header.Container', headerCtCfg);
105885             }
105886
105887             // Extract the array of Column objects
105888             me.columns = me.headerCt.items.items;
105889
105890             if (me.hideHeaders) {
105891                 me.headerCt.height = 0;
105892                 me.headerCt.border = false;
105893                 me.headerCt.addCls(Ext.baseCSSPrefix + 'grid-header-ct-hidden');
105894                 me.addCls(Ext.baseCSSPrefix + 'grid-header-hidden');
105895                 // IE Quirks Mode fix
105896                 // If hidden configuration option was used, several layout calculations will be bypassed.
105897                 if (Ext.isIEQuirks) {
105898                     me.headerCt.style = {
105899                         display: 'none'
105900                     };
105901                 }
105902             }
105903
105904             // turn both on.
105905             if (scroll === true || scroll === 'both') {
105906                 vertical = horizontal = true;
105907             } else if (scroll === 'horizontal') {
105908                 horizontal = true;
105909             } else if (scroll === 'vertical') {
105910                 vertical = true;
105911             // All other values become 'none' or false.
105912             } else {
105913                 me.headerCt.availableSpaceOffset = 0;
105914             }
105915
105916             if (vertical) {
105917                 me.verticalScroller = me.verticalScroller || {};
105918                 Ext.applyIf(me.verticalScroller, {
105919                     dock: me.verticalScrollDock,
105920                     xtype: me.verticalScrollerType,
105921                     store: me.store
105922                 });
105923                 me.verticalScroller = Ext.ComponentManager.create(me.verticalScroller);
105924                 me.mon(me.verticalScroller, {
105925                     bodyscroll: me.onVerticalScroll,
105926                     scope: me
105927                 });
105928             }
105929
105930             if (horizontal) {
105931                 me.horizontalScroller = Ext.ComponentManager.create({
105932                     xtype: 'gridscroller',
105933                     section: me,
105934                     dock: 'bottom',
105935                     store: me.store
105936                 });
105937                 me.mon(me.horizontalScroller, {
105938                     bodyscroll: me.onHorizontalScroll,
105939                     scope: me
105940                 });
105941             }
105942
105943             me.headerCt.on('columnresize', me.onHeaderResize, me);
105944             me.relayEvents(me.headerCt, ['columnresize', 'columnmove', 'columnhide', 'columnshow', 'sortchange']);
105945             me.features = me.features || [];
105946             me.dockedItems = me.dockedItems || [];
105947             me.dockedItems.unshift(me.headerCt);
105948             me.viewConfig = me.viewConfig || {};
105949             me.viewConfig.invalidateScrollerOnRefresh = me.invalidateScrollerOnRefresh;
105950
105951             // AbstractDataView will look up a Store configured as an object
105952             // getView converts viewConfig into a View instance
105953             view = me.getView();
105954
105955             if (view) {
105956                 me.mon(view.store, {
105957                     load: me.onStoreLoad,
105958                     scope: me
105959                 });
105960                 me.mon(view, {
105961                     refresh: {
105962                         fn: this.onViewRefresh,
105963                         scope: me,
105964                         buffer: 50
105965                     },
105966                     itemupdate: me.onViewItemUpdate,
105967                     scope: me
105968                 });
105969                 this.relayEvents(view, [
105970                     /**
105971                      * @event beforeitemmousedown
105972                      * Fires before the mousedown event on an item is processed. Returns false to cancel the default action.
105973                      * @param {Ext.view.View} this
105974                      * @param {Ext.data.Model} record The record that belongs to the item
105975                      * @param {HTMLElement} item The item's element
105976                      * @param {Number} index The item's index
105977                      * @param {Ext.EventObject} e The raw event object
105978                      */
105979                     'beforeitemmousedown',
105980                     /**
105981                      * @event beforeitemmouseup
105982                      * Fires before the mouseup event on an item is processed. Returns false to cancel the default action.
105983                      * @param {Ext.view.View} this
105984                      * @param {Ext.data.Model} record The record that belongs to the item
105985                      * @param {HTMLElement} item The item's element
105986                      * @param {Number} index The item's index
105987                      * @param {Ext.EventObject} e The raw event object
105988                      */
105989                     'beforeitemmouseup',
105990                     /**
105991                      * @event beforeitemmouseenter
105992                      * Fires before the mouseenter event on an item is processed. Returns false to cancel the default action.
105993                      * @param {Ext.view.View} this
105994                      * @param {Ext.data.Model} record The record that belongs to the item
105995                      * @param {HTMLElement} item The item's element
105996                      * @param {Number} index The item's index
105997                      * @param {Ext.EventObject} e The raw event object
105998                      */
105999                     'beforeitemmouseenter',
106000                     /**
106001                      * @event beforeitemmouseleave
106002                      * Fires before the mouseleave event on an item is processed. Returns false to cancel the default action.
106003                      * @param {Ext.view.View} this
106004                      * @param {Ext.data.Model} record The record that belongs to the item
106005                      * @param {HTMLElement} item The item's element
106006                      * @param {Number} index The item's index
106007                      * @param {Ext.EventObject} e The raw event object
106008                      */
106009                     'beforeitemmouseleave',
106010                     /**
106011                      * @event beforeitemclick
106012                      * Fires before the click event on an item is processed. Returns false to cancel the default action.
106013                      * @param {Ext.view.View} this
106014                      * @param {Ext.data.Model} record The record that belongs to the item
106015                      * @param {HTMLElement} item The item's element
106016                      * @param {Number} index The item's index
106017                      * @param {Ext.EventObject} e The raw event object
106018                      */
106019                     'beforeitemclick',
106020                     /**
106021                      * @event beforeitemdblclick
106022                      * Fires before the dblclick event on an item is processed. Returns false to cancel the default action.
106023                      * @param {Ext.view.View} this
106024                      * @param {Ext.data.Model} record The record that belongs to the item
106025                      * @param {HTMLElement} item The item's element
106026                      * @param {Number} index The item's index
106027                      * @param {Ext.EventObject} e The raw event object
106028                      */
106029                     'beforeitemdblclick',
106030                     /**
106031                      * @event beforeitemcontextmenu
106032                      * Fires before the contextmenu event on an item is processed. Returns false to cancel the default action.
106033                      * @param {Ext.view.View} this
106034                      * @param {Ext.data.Model} record The record that belongs to the item
106035                      * @param {HTMLElement} item The item's element
106036                      * @param {Number} index The item's index
106037                      * @param {Ext.EventObject} e The raw event object
106038                      */
106039                     'beforeitemcontextmenu',
106040                     /**
106041                      * @event itemmousedown
106042                      * Fires when there is a mouse down on an item
106043                      * @param {Ext.view.View} this
106044                      * @param {Ext.data.Model} record The record that belongs to the item
106045                      * @param {HTMLElement} item The item's element
106046                      * @param {Number} index The item's index
106047                      * @param {Ext.EventObject} e The raw event object
106048                      */
106049                     'itemmousedown',
106050                     /**
106051                      * @event itemmouseup
106052                      * Fires when there is a mouse up on an item
106053                      * @param {Ext.view.View} this
106054                      * @param {Ext.data.Model} record The record that belongs to the item
106055                      * @param {HTMLElement} item The item's element
106056                      * @param {Number} index The item's index
106057                      * @param {Ext.EventObject} e The raw event object
106058                      */
106059                     'itemmouseup',
106060                     /**
106061                      * @event itemmouseenter
106062                      * Fires when the mouse enters an item.
106063                      * @param {Ext.view.View} this
106064                      * @param {Ext.data.Model} record The record that belongs to the item
106065                      * @param {HTMLElement} item The item's element
106066                      * @param {Number} index The item's index
106067                      * @param {Ext.EventObject} e The raw event object
106068                      */
106069                     'itemmouseenter',
106070                     /**
106071                      * @event itemmouseleave
106072                      * Fires when the mouse leaves an item.
106073                      * @param {Ext.view.View} this
106074                      * @param {Ext.data.Model} record The record that belongs to the item
106075                      * @param {HTMLElement} item The item's element
106076                      * @param {Number} index The item's index
106077                      * @param {Ext.EventObject} e The raw event object
106078                      */
106079                     'itemmouseleave',
106080                     /**
106081                      * @event itemclick
106082                      * Fires when an item is clicked.
106083                      * @param {Ext.view.View} this
106084                      * @param {Ext.data.Model} record The record that belongs to the item
106085                      * @param {HTMLElement} item The item's element
106086                      * @param {Number} index The item's index
106087                      * @param {Ext.EventObject} e The raw event object
106088                      */
106089                     'itemclick',
106090                     /**
106091                      * @event itemdblclick
106092                      * Fires when an item is double clicked.
106093                      * @param {Ext.view.View} this
106094                      * @param {Ext.data.Model} record The record that belongs to the item
106095                      * @param {HTMLElement} item The item's element
106096                      * @param {Number} index The item's index
106097                      * @param {Ext.EventObject} e The raw event object
106098                      */
106099                     'itemdblclick',
106100                     /**
106101                      * @event itemcontextmenu
106102                      * Fires when an item is right clicked.
106103                      * @param {Ext.view.View} this
106104                      * @param {Ext.data.Model} record The record that belongs to the item
106105                      * @param {HTMLElement} item The item's element
106106                      * @param {Number} index The item's index
106107                      * @param {Ext.EventObject} e The raw event object
106108                      */
106109                     'itemcontextmenu',
106110                     /**
106111                      * @event beforecontainermousedown
106112                      * Fires before the mousedown event on the container is processed. Returns false to cancel the default action.
106113                      * @param {Ext.view.View} this
106114                      * @param {Ext.EventObject} e The raw event object
106115                      */
106116                     'beforecontainermousedown',
106117                     /**
106118                      * @event beforecontainermouseup
106119                      * Fires before the mouseup event on the container is processed. Returns false to cancel the default action.
106120                      * @param {Ext.view.View} this
106121                      * @param {Ext.EventObject} e The raw event object
106122                      */
106123                     'beforecontainermouseup',
106124                     /**
106125                      * @event beforecontainermouseover
106126                      * Fires before the mouseover event on the container is processed. Returns false to cancel the default action.
106127                      * @param {Ext.view.View} this
106128                      * @param {Ext.EventObject} e The raw event object
106129                      */
106130                     'beforecontainermouseover',
106131                     /**
106132                      * @event beforecontainermouseout
106133                      * Fires before the mouseout event on the container is processed. Returns false to cancel the default action.
106134                      * @param {Ext.view.View} this
106135                      * @param {Ext.EventObject} e The raw event object
106136                      */
106137                     'beforecontainermouseout',
106138                     /**
106139                      * @event beforecontainerclick
106140                      * Fires before the click event on the container is processed. Returns false to cancel the default action.
106141                      * @param {Ext.view.View} this
106142                      * @param {Ext.EventObject} e The raw event object
106143                      */
106144                     'beforecontainerclick',
106145                     /**
106146                      * @event beforecontainerdblclick
106147                      * Fires before the dblclick event on the container is processed. Returns false to cancel the default action.
106148                      * @param {Ext.view.View} this
106149                      * @param {Ext.EventObject} e The raw event object
106150                      */
106151                     'beforecontainerdblclick',
106152                     /**
106153                      * @event beforecontainercontextmenu
106154                      * Fires before the contextmenu event on the container is processed. Returns false to cancel the default action.
106155                      * @param {Ext.view.View} this
106156                      * @param {Ext.EventObject} e The raw event object
106157                      */
106158                     'beforecontainercontextmenu',
106159                     /**
106160                      * @event containermouseup
106161                      * Fires when there is a mouse up on the container
106162                      * @param {Ext.view.View} this
106163                      * @param {Ext.EventObject} e The raw event object
106164                      */
106165                     'containermouseup',
106166                     /**
106167                      * @event containermouseover
106168                      * Fires when you move the mouse over the container.
106169                      * @param {Ext.view.View} this
106170                      * @param {Ext.EventObject} e The raw event object
106171                      */
106172                     'containermouseover',
106173                     /**
106174                      * @event containermouseout
106175                      * Fires when you move the mouse out of the container.
106176                      * @param {Ext.view.View} this
106177                      * @param {Ext.EventObject} e The raw event object
106178                      */
106179                     'containermouseout',
106180                     /**
106181                      * @event containerclick
106182                      * Fires when the container is clicked.
106183                      * @param {Ext.view.View} this
106184                      * @param {Ext.EventObject} e The raw event object
106185                      */
106186                     'containerclick',
106187                     /**
106188                      * @event containerdblclick
106189                      * Fires when the container is double clicked.
106190                      * @param {Ext.view.View} this
106191                      * @param {Ext.EventObject} e The raw event object
106192                      */
106193                     'containerdblclick',
106194                     /**
106195                      * @event containercontextmenu
106196                      * Fires when the container is right clicked.
106197                      * @param {Ext.view.View} this
106198                      * @param {Ext.EventObject} e The raw event object
106199                      */
106200                     'containercontextmenu',
106201
106202                     /**
106203                      * @event selectionchange
106204                      * Fires when the selected nodes change. Relayed event from the underlying selection model.
106205                      * @param {Ext.view.View} this
106206                      * @param {Array} selections Array of the selected nodes
106207                      */
106208                     'selectionchange',
106209                     /**
106210                      * @event beforeselect
106211                      * Fires before a selection is made. If any handlers return false, the selection is cancelled.
106212                      * @param {Ext.view.View} this
106213                      * @param {HTMLElement} node The node to be selected
106214                      * @param {Array} selections Array of currently selected nodes
106215                      */
106216                     'beforeselect'
106217                 ]);
106218             }
106219         }
106220         me.callParent(arguments);
106221     },
106222
106223     // state management
106224     initStateEvents: function(){
106225         var events = this.stateEvents;
106226         // push on stateEvents if they don't exist
106227         Ext.each(['columnresize', 'columnmove', 'columnhide', 'columnshow', 'sortchange'], function(event){
106228             if (Ext.Array.indexOf(events, event)) {
106229                 events.push(event);
106230             }
106231         });
106232         this.callParent();
106233     },
106234
106235     getState: function(){
106236         var state = {
106237             columns: []
106238         },
106239         sorter = this.store.sorters.first();
106240
106241         this.headerCt.items.each(function(header){
106242             state.columns.push({
106243                 id: header.headerId,
106244                 width: header.flex ? undefined : header.width,
106245                 hidden: header.hidden,
106246                 sortable: header.sortable
106247             });
106248         });
106249
106250         if (sorter) {
106251             state.sort = {
106252                 property: sorter.property,
106253                 direction: sorter.direction
106254             };
106255         }
106256         return state;
106257     },
106258
106259     applyState: function(state) {
106260         var headers = state.columns,
106261             length = headers ? headers.length : 0,
106262             headerCt = this.headerCt,
106263             items = headerCt.items,
106264             sorter = state.sort,
106265             store = this.store,
106266             i = 0,
106267             index,
106268             headerState,
106269             header;
106270
106271         for (; i < length; ++i) {
106272             headerState = headers[i];
106273             header = headerCt.down('gridcolumn[headerId=' + headerState.id + ']');
106274             index = items.indexOf(header);
106275             if (i !== index) {
106276                 headerCt.moveHeader(index, i);
106277             }
106278             header.sortable = headerState.sortable;
106279             if (Ext.isDefined(headerState.width)) {
106280                 delete header.flex;
106281                 if (header.rendered) {
106282                     header.setWidth(headerState.width);
106283                 } else {
106284                     header.minWidth = header.width = headerState.width;
106285                 }
106286             }
106287             header.hidden = headerState.hidden;
106288         }
106289
106290         if (sorter) {
106291             if (store.remoteSort) {
106292                 store.sorters.add(Ext.create('Ext.util.Sorter', {
106293                     property: sorter.property,
106294                     direction: sorter.direction
106295                 }));
106296             }
106297             else {
106298                 store.sort(sorter.property, sorter.direction);
106299             }
106300         }
106301     },
106302
106303     /**
106304      * Returns the store associated with this Panel.
106305      * @return {Ext.data.Store} The store
106306      */
106307     getStore: function(){
106308         return this.store;
106309     },
106310
106311     /**
106312      * Gets the view for this panel.
106313      * @return {Ext.view.Table}
106314      */
106315     getView: function() {
106316         var me = this,
106317             sm;
106318
106319         if (!me.view) {
106320             sm = me.getSelectionModel();
106321             me.view = me.createComponent(Ext.apply({}, me.viewConfig, {
106322                 xtype: me.viewType,
106323                 store: me.store,
106324                 headerCt: me.headerCt,
106325                 selModel: sm,
106326                 features: me.features,
106327                 panel: me
106328             }));
106329             me.mon(me.view, {
106330                 uievent: me.processEvent,
106331                 scope: me
106332             });
106333             sm.view = me.view;
106334             me.headerCt.view = me.view;
106335             me.relayEvents(me.view, ['cellclick', 'celldblclick']);
106336         }
106337         return me.view;
106338     },
106339
106340     /**
106341      * @private
106342      * @override
106343      * autoScroll is never valid for all classes which extend TablePanel.
106344      */
106345     setAutoScroll: Ext.emptyFn,
106346
106347     // This method hijacks Ext.view.Table's el scroll method.
106348     // This enables us to keep the virtualized scrollbars in sync
106349     // with the view. It currently does NOT support animation.
106350     elScroll: function(direction, distance, animate) {
106351         var me = this,
106352             scroller;
106353
106354         if (direction === "up" || direction === "left") {
106355             distance = -distance;
106356         }
106357
106358         if (direction === "down" || direction === "up") {
106359             scroller = me.getVerticalScroller();
106360             scroller.scrollByDeltaY(distance);
106361         } else {
106362             scroller = me.getHorizontalScroller();
106363             scroller.scrollByDeltaX(distance);
106364         }
106365     },
106366     
106367     afterLayout: function() {
106368         this.callParent(arguments);
106369         this.injectView();
106370     },
106371     
106372
106373     /**
106374      * @private
106375      * Called after this Component has achieved its correct initial size, after all layouts have done their thing.
106376      * This is so we can add the View only after the initial size is known. This method is buffered 30ms.
106377      */
106378     injectView: function() {
106379         if (!this.hasView && !this.collapsed) {
106380             var me   = this,
106381                 view = me.getView();
106382
106383             me.hasView = true;
106384             me.add(view);
106385
106386             // hijack the view el's scroll method
106387             view.el.scroll = Ext.Function.bind(me.elScroll, me);
106388             // We use to listen to document.body wheel events, but that's a
106389             // little much. We scope just to the view now.
106390             me.mon(view.el, {
106391                 mousewheel: me.onMouseWheel,
106392                 scope: me
106393             });
106394         }
106395     },
106396
106397     afterExpand: function() {
106398         this.callParent(arguments);
106399         if (!this.hasView) {
106400             this.injectView();
106401         }
106402     },
106403
106404     /**
106405      * @private
106406      * Process UI events from the view. Propagate them to whatever internal Components need to process them
106407      * @param {String} type Event type, eg 'click'
106408      * @param {TableView} view TableView Component
106409      * @param {HtmlElement} cell Cell HtmlElement the event took place within
106410      * @param {Number} recordIndex Index of the associated Store Model (-1 if none)
106411      * @param {Number} cellIndex Cell index within the row
106412      * @param {EventObject} e Original event
106413      */
106414     processEvent: function(type, view, cell, recordIndex, cellIndex, e) {
106415         var me = this,
106416             header;
106417
106418         if (cellIndex !== -1) {
106419             header = me.headerCt.getGridColumns()[cellIndex];
106420             return header.processEvent.apply(header, arguments);
106421         }
106422     },
106423
106424     /**
106425      * Request a recalculation of scrollbars and put them in if they are needed.
106426      */
106427     determineScrollbars: function() {
106428         var me = this,
106429             viewElDom,
106430             centerScrollWidth,
106431             centerClientWidth,
106432             scrollHeight,
106433             clientHeight;
106434
106435         if (!me.collapsed && me.view && me.view.el) {
106436             viewElDom = me.view.el.dom;
106437             //centerScrollWidth = viewElDom.scrollWidth;
106438             centerScrollWidth = me.headerCt.getFullWidth();
106439             /**
106440              * clientWidth often returns 0 in IE resulting in an
106441              * infinity result, here we use offsetWidth bc there are
106442              * no possible scrollbars and we don't care about margins
106443              */
106444             centerClientWidth = viewElDom.offsetWidth;
106445             if (me.verticalScroller && me.verticalScroller.el) {
106446                 scrollHeight = me.verticalScroller.getSizeCalculation().height;
106447             } else {
106448                 scrollHeight = viewElDom.scrollHeight;
106449             }
106450
106451             clientHeight = viewElDom.clientHeight;
106452
106453             if (!me.collapsed && scrollHeight > clientHeight) {
106454                 me.showVerticalScroller();
106455             } else {
106456                 me.hideVerticalScroller();
106457             }
106458
106459             if (!me.collapsed && centerScrollWidth > (centerClientWidth + Ext.getScrollBarWidth() - 2)) {
106460                 me.showHorizontalScroller();
106461             } else {
106462                 me.hideHorizontalScroller();
106463             }
106464         }
106465     },
106466
106467     onHeaderResize: function() {
106468         if (this.view && this.view.rendered) {
106469             this.determineScrollbars();
106470             this.invalidateScroller();
106471         }
106472     },
106473
106474     /**
106475      * Hide the verticalScroller and remove the horizontalScrollerPresentCls.
106476      */
106477     hideHorizontalScroller: function() {
106478         var me = this;
106479
106480         if (me.horizontalScroller && me.horizontalScroller.ownerCt === me) {
106481             me.verticalScroller.offsets.bottom = 0;
106482             me.removeDocked(me.horizontalScroller, false);
106483             me.removeCls(me.horizontalScrollerPresentCls);
106484             me.fireEvent('scrollerhide', me.horizontalScroller, 'horizontal');
106485         }
106486
106487     },
106488
106489     /**
106490      * Show the horizontalScroller and add the horizontalScrollerPresentCls.
106491      */
106492     showHorizontalScroller: function() {
106493         var me = this;
106494
106495         if (me.verticalScroller) {
106496             me.verticalScroller.offsets.bottom = Ext.getScrollBarWidth() - 2;
106497         }
106498         if (me.horizontalScroller && me.horizontalScroller.ownerCt !== me) {
106499             me.addDocked(me.horizontalScroller);
106500             me.addCls(me.horizontalScrollerPresentCls);
106501             me.fireEvent('scrollershow', me.horizontalScroller, 'horizontal');
106502         }
106503     },
106504
106505     /**
106506      * Hide the verticalScroller and remove the verticalScrollerPresentCls.
106507      */
106508     hideVerticalScroller: function() {
106509         var me = this,
106510             headerCt = me.headerCt;
106511
106512         // only trigger a layout when reserveOffset is changing
106513         if (headerCt && headerCt.layout.reserveOffset) {
106514             headerCt.layout.reserveOffset = false;
106515             headerCt.doLayout();
106516         }
106517         if (me.verticalScroller && me.verticalScroller.ownerCt === me) {
106518             me.removeDocked(me.verticalScroller, false);
106519             me.removeCls(me.verticalScrollerPresentCls);
106520             me.fireEvent('scrollerhide', me.verticalScroller, 'vertical');
106521         }
106522     },
106523
106524     /**
106525      * Show the verticalScroller and add the verticalScrollerPresentCls.
106526      */
106527     showVerticalScroller: function() {
106528         var me = this,
106529             headerCt = me.headerCt;
106530
106531         // only trigger a layout when reserveOffset is changing
106532         if (headerCt && !headerCt.layout.reserveOffset) {
106533             headerCt.layout.reserveOffset = true;
106534             headerCt.doLayout();
106535         }
106536         if (me.verticalScroller && me.verticalScroller.ownerCt !== me) {
106537             me.addDocked(me.verticalScroller);
106538             me.addCls(me.verticalScrollerPresentCls);
106539             me.fireEvent('scrollershow', me.verticalScroller, 'vertical');
106540         }
106541     },
106542
106543     /**
106544      * Invalides scrollers that are present and forces a recalculation.
106545      * (Not related to showing/hiding the scrollers)
106546      */
106547     invalidateScroller: function() {
106548         var me = this,
106549             vScroll = me.verticalScroller,
106550             hScroll = me.horizontalScroller;
106551
106552         if (vScroll) {
106553             vScroll.invalidate();
106554         }
106555         if (hScroll) {
106556             hScroll.invalidate();
106557         }
106558     },
106559
106560     // refresh the view when a header moves
106561     onHeaderMove: function(headerCt, header, fromIdx, toIdx) {
106562         this.view.refresh();
106563     },
106564
106565     // Section onHeaderHide is invoked after view.
106566     onHeaderHide: function(headerCt, header) {
106567         this.invalidateScroller();
106568     },
106569
106570     onHeaderShow: function(headerCt, header) {
106571         this.invalidateScroller();
106572     },
106573
106574     getVerticalScroller: function() {
106575         return this.getScrollerOwner().down('gridscroller[dock=' + this.verticalScrollDock + ']');
106576     },
106577
106578     getHorizontalScroller: function() {
106579         return this.getScrollerOwner().down('gridscroller[dock=bottom]');
106580     },
106581
106582     onMouseWheel: function(e) {
106583         var me = this,
106584             browserEvent = e.browserEvent,
106585             vertScroller = me.getVerticalScroller(),
106586             horizScroller = me.getHorizontalScroller(),
106587             scrollDelta = me.scrollDelta,
106588             deltaY, deltaX,
106589             vertScrollerEl, horizScrollerEl,
106590             origScrollLeft, origScrollTop,
106591             newScrollLeft, newScrollTop;
106592
106593         // Track original scroll values, so we can see if we've
106594         // reached the end of our scroll height/width.
106595         if (horizScroller) {
106596             horizScrollerEl = horizScroller.el;
106597             if (horizScrollerEl) {
106598                 origScrollLeft = horizScrollerEl.dom.scrollLeft;
106599             }
106600         }
106601         if (vertScroller) {
106602             vertScrollerEl = vertScroller.el;
106603             if (vertScrollerEl) {
106604                 origScrollTop = vertScrollerEl.dom.scrollTop;
106605             }
106606         }
106607
106608         // Webkit Horizontal Axis
106609         if (browserEvent.wheelDeltaX || browserEvent.wheelDeltaY) {
106610             deltaX = -browserEvent.wheelDeltaX / 120 * scrollDelta / 3;
106611             deltaY = -browserEvent.wheelDeltaY / 120 * scrollDelta / 3;
106612             if (horizScroller) {
106613                 newScrollLeft = horizScroller.scrollByDeltaX(deltaX);
106614             }
106615             if (vertScroller) {
106616                 newScrollTop = vertScroller.scrollByDeltaY(deltaY);
106617             }
106618         } else {
106619             // Gecko Horizontal Axis
106620             if (browserEvent.axis && browserEvent.axis === 1) {
106621                 if (horizScroller) {
106622                     deltaX = -(scrollDelta * e.getWheelDelta()) / 3;
106623                     newScrollLeft = horizScroller.scrollByDeltaX(deltaX);
106624                 }
106625             } else {
106626                 if (vertScroller) {
106627
106628                     deltaY = -(scrollDelta * e.getWheelDelta() / 3);
106629                     newScrollTop = vertScroller.scrollByDeltaY(deltaY);
106630                 }
106631             }
106632         }
106633
106634         // If after given our delta, the scroller has not progressed, then we're
106635         // at the end of our scroll range and shouldn't stop the browser event.
106636         if ((deltaX !== 0 && newScrollLeft !== origScrollLeft) ||
106637             (deltaY !== 0 && newScrollTop !== origScrollTop)) {
106638             e.stopEvent();
106639         }
106640     },
106641
106642     /**
106643      * @private
106644      * Determine and invalidate scrollers on view refresh
106645      */
106646     onViewRefresh: function() {
106647         if (Ext.isIE) {
106648             this.syncCellHeight();
106649         }
106650         this.determineScrollbars();
106651         if (this.invalidateScrollerOnRefresh) {
106652             this.invalidateScroller();
106653         }
106654     },
106655
106656     onViewItemUpdate: function(record, index, tr) {
106657         if (Ext.isIE) {
106658             this.syncCellHeight([tr]);
106659         }
106660     },
106661
106662     // BrowserBug: IE will not stretch the td to fit the height of the entire
106663     // tr, so manually sync cellheights on refresh and when an item has been
106664     // updated.
106665     syncCellHeight: function(trs) {
106666         var me    = this,
106667             i     = 0,
106668             tds,
106669             j, tdsLn,
106670             tr, td,
106671             trsLn,
106672             rowHeights = [],
106673             cellHeights,
106674             cellClsSelector = ('.' + Ext.baseCSSPrefix + 'grid-cell');
106675
106676         trs   = trs || me.view.getNodes();
106677         
106678         trsLn = trs.length;
106679         // Reading loop
106680         for (; i < trsLn; i++) {
106681             tr = trs[i];
106682             tds = Ext.fly(tr).query(cellClsSelector);
106683             tdsLn = tds.length;
106684             cellHeights = [];
106685             for (j = 0; j < tdsLn; j++) {
106686                 td = tds[j];
106687                 cellHeights.push(td.clientHeight);
106688             }
106689             rowHeights.push(Ext.Array.max(cellHeights));
106690         }
106691
106692         // Setting loop
106693         for (i = 0; i < trsLn; i++) {
106694             tr = trs[i];
106695             tdsLn = tr.childNodes.length;
106696             for (j = 0; j < tdsLn; j++) {
106697                 td = Ext.fly(tr.childNodes[j]);
106698                 if (rowHeights[i]) {
106699                     if (td.is(cellClsSelector)) {
106700                         td.setHeight(rowHeights[i]);
106701                     } else {
106702                         td.down(cellClsSelector).setHeight(rowHeights[i]);
106703                     }
106704                 }
106705                 
106706             }
106707         }
106708     },
106709
106710     /**
106711      * Sets the scrollTop of the TablePanel.
106712      * @param {Number} deltaY
106713      */
106714     setScrollTop: function(top) {
106715         var me               = this,
106716             rootCmp          = me.getScrollerOwner(),
106717             verticalScroller = me.getVerticalScroller();
106718
106719         rootCmp.virtualScrollTop = top;
106720         if (verticalScroller) {
106721             verticalScroller.setScrollTop(top);
106722         }
106723
106724     },
106725
106726     getScrollerOwner: function() {
106727         var rootCmp = this;
106728         if (!this.scrollerOwner) {
106729             rootCmp = this.up('[scrollerOwner]');
106730         }
106731         return rootCmp;
106732     },
106733
106734     /**
106735      * Scrolls the TablePanel by deltaY
106736      * @param {Number} deltaY
106737      */
106738     scrollByDeltaY: function(deltaY) {
106739         var rootCmp = this.getScrollerOwner(),
106740             scrollerRight;
106741         scrollerRight = rootCmp.down('gridscroller[dock=' + this.verticalScrollDock + ']');
106742         if (scrollerRight) {
106743             scrollerRight.scrollByDeltaY(deltaY);
106744         }
106745     },
106746
106747
106748     /**
106749      * Scrolls the TablePanel by deltaX
106750      * @param {Number} deltaY
106751      */
106752     scrollByDeltaX: function(deltaX) {
106753         this.horizontalScroller.scrollByDeltaX(deltaX);
106754     },
106755
106756     /**
106757      * Get left hand side marker for header resizing.
106758      * @private
106759      */
106760     getLhsMarker: function() {
106761         var me = this;
106762
106763         if (!me.lhsMarker) {
106764             me.lhsMarker = Ext.core.DomHelper.append(me.el, {
106765                 cls: Ext.baseCSSPrefix + 'grid-resize-marker'
106766             }, true);
106767         }
106768         return me.lhsMarker;
106769     },
106770
106771     /**
106772      * Get right hand side marker for header resizing.
106773      * @private
106774      */
106775     getRhsMarker: function() {
106776         var me = this;
106777
106778         if (!me.rhsMarker) {
106779             me.rhsMarker = Ext.core.DomHelper.append(me.el, {
106780                 cls: Ext.baseCSSPrefix + 'grid-resize-marker'
106781             }, true);
106782         }
106783         return me.rhsMarker;
106784     },
106785
106786     /**
106787      * Returns the selection model being used and creates it via the configuration
106788      * if it has not been created already.
106789      * @return {Ext.selection.Model} selModel
106790      */
106791     getSelectionModel: function(){
106792         if (!this.selModel) {
106793             this.selModel = {};
106794         }
106795
106796         var mode = 'SINGLE',
106797             type;
106798         if (this.simpleSelect) {
106799             mode = 'SIMPLE';
106800         } else if (this.multiSelect) {
106801             mode = 'MULTI';
106802         }
106803
106804         Ext.applyIf(this.selModel, {
106805             allowDeselect: this.allowDeselect,
106806             mode: mode
106807         });
106808
106809         if (!this.selModel.events) {
106810             type = this.selModel.selType || this.selType;
106811             this.selModel = Ext.create('selection.' + type, this.selModel);
106812         }
106813
106814         if (!this.selModel.hasRelaySetup) {
106815             this.relayEvents(this.selModel, ['selectionchange', 'select', 'deselect']);
106816             this.selModel.hasRelaySetup = true;
106817         }
106818
106819         // lock the selection model if user
106820         // has disabled selection
106821         if (this.disableSelection) {
106822             this.selModel.locked = true;
106823         }
106824         return this.selModel;
106825     },
106826
106827     onVerticalScroll: function(event, target) {
106828         var owner = this.getScrollerOwner(),
106829             items = owner.query('tableview'),
106830             i = 0,
106831             len = items.length;
106832
106833         for (; i < len; i++) {
106834             items[i].el.dom.scrollTop = target.scrollTop;
106835         }
106836     },
106837
106838     onHorizontalScroll: function(event, target) {
106839         var owner = this.getScrollerOwner(),
106840             items = owner.query('tableview'),
106841             i = 0,
106842             len = items.length,
106843             center,
106844             centerEl,
106845             centerScrollWidth,
106846             centerClientWidth,
106847             width;
106848
106849         center = items[1] || items[0];
106850         centerEl = center.el.dom;
106851         centerScrollWidth = centerEl.scrollWidth;
106852         centerClientWidth = centerEl.offsetWidth;
106853         width = this.horizontalScroller.getWidth();
106854
106855         centerEl.scrollLeft = target.scrollLeft;
106856         this.headerCt.el.dom.scrollLeft = target.scrollLeft;
106857     },
106858
106859     // template method meant to be overriden
106860     onStoreLoad: Ext.emptyFn,
106861
106862     getEditorParent: function() {
106863         return this.body;
106864     },
106865
106866     bindStore: function(store) {
106867         var me = this;
106868         me.store = store;
106869         me.getView().bindStore(store);
106870     },
106871
106872     reconfigure: function(store, columns) {
106873         var me = this;
106874
106875         if (me.lockable) {
106876             me.reconfigureLockable(store, columns);
106877             return;
106878         }
106879
106880         if (columns) {
106881             me.headerCt.removeAll();
106882             me.headerCt.add(columns);
106883         }
106884         if (store) {
106885             store = Ext.StoreManager.lookup(store);
106886             me.bindStore(store);
106887         } else {
106888             me.getView().refresh();
106889         }
106890     },
106891
106892     afterComponentLayout: function() {
106893         this.callParent(arguments);
106894         this.determineScrollbars();
106895         this.invalidateScroller();
106896     }
106897 });
106898 /**
106899  * @class Ext.view.Table
106900  * @extends Ext.view.View
106901
106902 This class encapsulates the user interface for a tabular data set.
106903 It acts as a centralized manager for controlling the various interface
106904 elements of the view. This includes handling events, such as row and cell
106905 level based DOM events. It also reacts to events from the underlying {@link Ext.selection.Model}
106906 to provide visual feedback to the user. 
106907
106908 This class does not provide ways to manipulate the underlying data of the configured
106909 {@link Ext.data.Store}.
106910
106911 This is the base class for both {@link Ext.grid.View} and {@link Ext.tree.View} and is not
106912 to be used directly.
106913
106914  * @markdown
106915  * @abstract
106916  * @xtype tableview
106917  * @author Nicolas Ferrero
106918  */
106919 Ext.define('Ext.view.Table', {
106920     extend: 'Ext.view.View',
106921     alias: 'widget.tableview',
106922     uses: [
106923         'Ext.view.TableChunker',
106924         'Ext.util.DelayedTask',
106925         'Ext.util.MixedCollection'
106926     ],
106927
106928     cls: Ext.baseCSSPrefix + 'grid-view',
106929
106930     // row
106931     itemSelector: '.' + Ext.baseCSSPrefix + 'grid-row',
106932     // cell
106933     cellSelector: '.' + Ext.baseCSSPrefix + 'grid-cell',
106934
106935     selectedItemCls: Ext.baseCSSPrefix + 'grid-row-selected',
106936     selectedCellCls: Ext.baseCSSPrefix + 'grid-cell-selected',
106937     focusedItemCls: Ext.baseCSSPrefix + 'grid-row-focused',
106938     overItemCls: Ext.baseCSSPrefix + 'grid-row-over',
106939     altRowCls:   Ext.baseCSSPrefix + 'grid-row-alt',
106940     rowClsRe: /(?:^|\s*)grid-row-(first|last|alt)(?:\s+|$)/g,
106941     cellRe: new RegExp('x-grid-cell-([^\\s]+) ', ''),
106942
106943     // cfg docs inherited
106944     trackOver: true,
106945
106946     /**
106947      * Override this function to apply custom CSS classes to rows during rendering.  You can also supply custom
106948      * parameters to the row template for the current row to customize how it is rendered using the <b>rowParams</b>
106949      * parameter.  This function should return the CSS class name (or empty string '' for none) that will be added
106950      * to the row's wrapping div.  To apply multiple class names, simply return them space-delimited within the string
106951      * (e.g., 'my-class another-class'). Example usage:
106952     <pre><code>
106953 viewConfig: {
106954     forceFit: true,
106955     showPreview: true, // custom property
106956     enableRowBody: true, // required to create a second, full-width row to show expanded Record data
106957     getRowClass: function(record, rowIndex, rp, ds){ // rp = rowParams
106958         if(this.showPreview){
106959             rp.body = '&lt;p>'+record.data.excerpt+'&lt;/p>';
106960             return 'x-grid3-row-expanded';
106961         }
106962         return 'x-grid3-row-collapsed';
106963     }
106964 },
106965     </code></pre>
106966      * @param {Model} model The {@link Ext.data.Model} corresponding to the current row.
106967      * @param {Number} index The row index.
106968      * @param {Object} rowParams (DEPRECATED) A config object that is passed to the row template during rendering that allows
106969      * customization of various aspects of a grid row.
106970      * <p>If {@link #enableRowBody} is configured <b><tt></tt>true</b>, then the following properties may be set
106971      * by this function, and will be used to render a full-width expansion row below each grid row:</p>
106972      * <ul>
106973      * <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>
106974      * <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>
106975      * </ul>
106976      * The following property will be passed in, and may be appended to:
106977      * <ul>
106978      * <li><code>tstyle</code> : String <div class="sub-desc">A CSS style specification that willl be applied to the &lt;table> element which encapsulates
106979      * both the standard grid row, and any expansion row.</div></li>
106980      * </ul>
106981      * @param {Store} store The {@link Ext.data.Store} this grid is bound to
106982      * @method getRowClass
106983      * @return {String} a CSS class name to add to the row.
106984      */
106985     getRowClass: null,
106986
106987     initComponent: function() {
106988         this.scrollState = {};
106989         this.selModel.view = this;
106990         this.headerCt.view = this;
106991         this.initFeatures();
106992         this.setNewTemplate();
106993         this.callParent();
106994         this.mon(this.store, {
106995             load: this.onStoreLoad,
106996             scope: this
106997         });
106998
106999         // this.addEvents(
107000         //     /**
107001         //      * @event rowfocus
107002         //      * @param {Ext.data.Record} record
107003         //      * @param {HTMLElement} row
107004         //      * @param {Number} rowIdx
107005         //      */
107006         //     'rowfocus'
107007         // );
107008     },
107009
107010     // scroll to top of the grid when store loads
107011     onStoreLoad: function(){
107012         if (this.invalidateScrollerOnRefresh) {
107013             if (Ext.isGecko) {
107014                 if (!this.scrollToTopTask) {
107015                     this.scrollToTopTask = Ext.create('Ext.util.DelayedTask', this.scrollToTop, this);
107016                 }
107017                 this.scrollToTopTask.delay(1);
107018             } else {
107019                 this.scrollToTop();
107020             }
107021         }
107022     },
107023
107024     // scroll the view to the top
107025     scrollToTop: Ext.emptyFn,
107026     
107027     /**
107028      * Get the columns used for generating a template via TableChunker.
107029      * See {@link Ext.grid.header.Container#getGridColumns}.
107030      * @private
107031      */
107032     getGridColumns: function() {
107033         return this.headerCt.getGridColumns();    
107034     },
107035     
107036     /**
107037      * Get a leaf level header by index regardless of what the nesting
107038      * structure is.
107039      * @private
107040      * @param {Number} index The index
107041      */
107042     getHeaderAtIndex: function(index) {
107043         return this.headerCt.getHeaderAtIndex(index);
107044     },
107045     
107046     /**
107047      * Get the cell (td) for a particular record and column.
107048      * @param {Ext.data.Model} record
107049      * @param {Ext.grid.column.Colunm} column
107050      * @private
107051      */
107052     getCell: function(record, column) {
107053         var row = this.getNode(record);
107054         return Ext.fly(row).down(column.getCellSelector());
107055     },
107056
107057     /**
107058      * Get a reference to a feature
107059      * @param {String} id The id of the feature
107060      * @return {Ext.grid.feature.Feature} The feature. Undefined if not found
107061      */
107062     getFeature: function(id) {
107063         var features = this.featuresMC;
107064         if (features) {
107065             return features.get(id);
107066         }
107067     },
107068
107069     /**
107070      * Initializes each feature and bind it to this view.
107071      * @private
107072      */
107073     initFeatures: function() {
107074         this.features = this.features || [];
107075         var features = this.features,
107076             ln       = features.length,
107077             i        = 0;
107078
107079         this.featuresMC = Ext.create('Ext.util.MixedCollection');
107080         for (; i < ln; i++) {
107081             // ensure feature hasnt already been instantiated
107082             if (!features[i].isFeature) {
107083                 features[i] = Ext.create('feature.'+features[i].ftype, features[i]);
107084             }
107085             // inject a reference to view
107086             features[i].view = this;
107087             this.featuresMC.add(features[i]);
107088         }
107089     },
107090
107091     /**
107092      * Gives features an injection point to attach events to the markup that
107093      * has been created for this view.
107094      * @private
107095      */
107096     attachEventsForFeatures: function() {
107097         var features = this.features,
107098             ln       = features.length,
107099             i        = 0;
107100
107101         for (; i < ln; i++) {
107102             if (features[i].isFeature) {
107103                 features[i].attachEvents();
107104             }
107105         }
107106     },
107107
107108     afterRender: function() {
107109         this.callParent();
107110         this.mon(this.el, {
107111             scroll: this.fireBodyScroll,
107112             scope: this
107113         });
107114         this.attachEventsForFeatures();
107115     },
107116
107117     fireBodyScroll: function(e, t) {
107118         this.fireEvent('bodyscroll', e, t);
107119     },
107120
107121     // TODO: Refactor headerCt dependency here to colModel
107122     /**
107123      * Uses the headerCt to transform data from dataIndex keys in a record to
107124      * headerId keys in each header and then run them through each feature to
107125      * get additional data for variables they have injected into the view template.
107126      * @private
107127      */
107128     prepareData: function(data, idx, record) {
107129         var orig     = this.headerCt.prepareData(data, idx, record, this),
107130             features = this.features,
107131             ln       = features.length,
107132             i        = 0,
107133             node, feature;
107134
107135         for (; i < ln; i++) {
107136             feature = features[i];
107137             if (feature.isFeature) {
107138                 Ext.apply(orig, feature.getAdditionalData(data, idx, record, orig, this));
107139             }
107140         }
107141
107142         return orig;
107143     },
107144
107145     // TODO: Refactor headerCt dependency here to colModel
107146     collectData: function(records, startIndex) {
107147         var preppedRecords = this.callParent(arguments),
107148             headerCt  = this.headerCt,
107149             fullWidth = headerCt.getFullWidth(),
107150             features  = this.features,
107151             ln = features.length,
107152             o = {
107153                 rows: preppedRecords,
107154                 fullWidth: fullWidth
107155             },
107156             i  = 0,
107157             feature,
107158             j = 0,
107159             jln,
107160             rowParams;
107161
107162         jln = preppedRecords.length;
107163         // process row classes, rowParams has been deprecated and has been moved
107164         // to the individual features that implement the behavior. 
107165         if (this.getRowClass) {
107166             for (; j < jln; j++) {
107167                 rowParams = {};
107168                 preppedRecords[j]['rowCls'] = this.getRowClass(records[j], j, rowParams, this.store);
107169                 if (rowParams.alt) {
107170                     Ext.Error.raise("The getRowClass alt property is no longer supported.");
107171                 }
107172                 if (rowParams.tstyle) {
107173                     Ext.Error.raise("The getRowClass tstyle property is no longer supported.");
107174                 }
107175                 if (rowParams.cells) {
107176                     Ext.Error.raise("The getRowClass cells property is no longer supported.");
107177                 }
107178                 if (rowParams.body) {
107179                     Ext.Error.raise("The getRowClass body property is no longer supported. Use the getAdditionalData method of the rowbody feature.");
107180                 }
107181                 if (rowParams.bodyStyle) {
107182                     Ext.Error.raise("The getRowClass bodyStyle property is no longer supported.");
107183                 }
107184                 if (rowParams.cols) {
107185                     Ext.Error.raise("The getRowClass cols property is no longer supported.");
107186                 }
107187             }
107188         }
107189         // currently only one feature may implement collectData. This is to modify
107190         // what's returned to the view before its rendered
107191         for (; i < ln; i++) {
107192             feature = features[i];
107193             if (feature.isFeature && feature.collectData && !feature.disabled) {
107194                 o = feature.collectData(records, preppedRecords, startIndex, fullWidth, o);
107195                 break;
107196             }
107197         }
107198         return o;
107199     },
107200
107201     // TODO: Refactor header resizing to column resizing
107202     /**
107203      * When a header is resized, setWidth on the individual columns resizer class,
107204      * the top level table, save/restore scroll state, generate a new template and
107205      * restore focus to the grid view's element so that keyboard navigation
107206      * continues to work.
107207      * @private
107208      */
107209     onHeaderResize: function(header, w, suppressFocus) {
107210         var el = this.el;
107211         if (el) {
107212             this.saveScrollState();
107213             // Grab the col and set the width, css
107214             // class is generated in TableChunker.
107215             // Select composites because there may be several chunks.
107216             el.select('.' + Ext.baseCSSPrefix + 'grid-col-resizer-'+header.id).setWidth(w);
107217             el.select('.' + Ext.baseCSSPrefix + 'grid-table-resizer').setWidth(this.headerCt.getFullWidth());
107218             this.restoreScrollState();
107219             this.setNewTemplate();
107220             if (!suppressFocus) {
107221                 this.el.focus();
107222             }
107223         }
107224     },
107225
107226     /**
107227      * When a header is shown restore its oldWidth if it was previously hidden.
107228      * @private
107229      */
107230     onHeaderShow: function(headerCt, header, suppressFocus) {
107231         // restore headers that were dynamically hidden
107232         if (header.oldWidth) {
107233             this.onHeaderResize(header, header.oldWidth, suppressFocus);
107234             delete header.oldWidth;
107235         // flexed headers will have a calculated size set
107236         // this additional check has to do with the fact that
107237         // defaults: {width: 100} will fight with a flex value
107238         } else if (header.width && !header.flex) {
107239             this.onHeaderResize(header, header.width, suppressFocus);
107240         }
107241         this.setNewTemplate();
107242     },
107243
107244     /**
107245      * When the header hides treat it as a resize to 0.
107246      * @private
107247      */
107248     onHeaderHide: function(headerCt, header, suppressFocus) {
107249         this.onHeaderResize(header, 0, suppressFocus);
107250     },
107251
107252     /**
107253      * Set a new template based on the current columns displayed in the
107254      * grid.
107255      * @private
107256      */
107257     setNewTemplate: function() {
107258         var columns = this.headerCt.getColumnsForTpl(true);
107259         this.tpl = this.getTableChunker().getTableTpl({
107260             columns: columns,
107261             features: this.features
107262         });
107263     },
107264
107265     /**
107266      * Get the configured chunker or default of Ext.view.TableChunker
107267      */
107268     getTableChunker: function() {
107269         return this.chunker || Ext.view.TableChunker;
107270     },
107271
107272     /**
107273      * Add a CSS Class to a specific row.
107274      * @param {HTMLElement/String/Number/Ext.data.Model} rowInfo An HTMLElement, index or instance of a model representing this row
107275      * @param {String} cls
107276      */
107277     addRowCls: function(rowInfo, cls) {
107278         var row = this.getNode(rowInfo);
107279         if (row) {
107280             Ext.fly(row).addCls(cls);
107281         }
107282     },
107283
107284     /**
107285      * Remove a CSS Class from a specific row.
107286      * @param {HTMLElement/String/Number/Ext.data.Model} rowInfo An HTMLElement, index or instance of a model representing this row
107287      * @param {String} cls
107288      */
107289     removeRowCls: function(rowInfo, cls) {
107290         var row = this.getNode(rowInfo);
107291         if (row) {
107292             Ext.fly(row).removeCls(cls);
107293         }
107294     },
107295
107296     // GridSelectionModel invokes onRowSelect as selection changes
107297     onRowSelect : function(rowIdx) {
107298         this.addRowCls(rowIdx, this.selectedItemCls);
107299     },
107300
107301     // GridSelectionModel invokes onRowDeselect as selection changes
107302     onRowDeselect : function(rowIdx) {
107303         this.removeRowCls(rowIdx, this.selectedItemCls);
107304         this.removeRowCls(rowIdx, this.focusedItemCls);
107305     },
107306     
107307     onCellSelect: function(position) {
107308         var cell = this.getCellByPosition(position);
107309         if (cell) {
107310             cell.addCls(this.selectedCellCls);
107311         }
107312     },
107313     
107314     onCellDeselect: function(position) {
107315         var cell = this.getCellByPosition(position);
107316         if (cell) {
107317             cell.removeCls(this.selectedCellCls);
107318         }
107319         
107320     },
107321     
107322     onCellFocus: function(position) {
107323         //var cell = this.getCellByPosition(position);
107324         this.focusCell(position);
107325     },
107326     
107327     getCellByPosition: function(position) {
107328         var row    = position.row,
107329             column = position.column,
107330             store  = this.store,
107331             node   = this.getNode(row),
107332             header = this.headerCt.getHeaderAtIndex(column),
107333             cellSelector,
107334             cell = false;
107335             
107336         if (header) {
107337             cellSelector = header.getCellSelector();
107338             cell = Ext.fly(node).down(cellSelector);
107339         }
107340         return cell;
107341     },
107342
107343     // GridSelectionModel invokes onRowFocus to 'highlight'
107344     // the last row focused
107345     onRowFocus: function(rowIdx, highlight, supressFocus) {
107346         var row = this.getNode(rowIdx);
107347
107348         if (highlight) {
107349             this.addRowCls(rowIdx, this.focusedItemCls);
107350             if (!supressFocus) {
107351                 this.focusRow(rowIdx);
107352             }
107353             //this.el.dom.setAttribute('aria-activedescendant', row.id);
107354         } else {
107355             this.removeRowCls(rowIdx, this.focusedItemCls);
107356         }
107357     },
107358
107359     /**
107360      * Focus a particular row and bring it into view. Will fire the rowfocus event.
107361      * @cfg {Mixed} An HTMLElement template node, index of a template node, the
107362      * id of a template node or the record associated with the node.
107363      */
107364     focusRow: function(rowIdx) {
107365         var row        = this.getNode(rowIdx),
107366             el         = this.el,
107367             adjustment = 0,
107368             panel      = this.ownerCt,
107369             rowRegion,
107370             elRegion,
107371             record;
107372             
107373         if (row && this.el) {
107374             elRegion  = el.getRegion();
107375             rowRegion = Ext.fly(row).getRegion();
107376             // row is above
107377             if (rowRegion.top < elRegion.top) {
107378                 adjustment = rowRegion.top - elRegion.top;
107379             // row is below
107380             } else if (rowRegion.bottom > elRegion.bottom) {
107381                 adjustment = rowRegion.bottom - elRegion.bottom;
107382             }
107383             record = this.getRecord(row);
107384             rowIdx = this.store.indexOf(record);
107385
107386             if (adjustment) {
107387                 // scroll the grid itself, so that all gridview's update.
107388                 panel.scrollByDeltaY(adjustment);
107389             }
107390             this.fireEvent('rowfocus', record, row, rowIdx);
107391         }
107392     },
107393
107394     focusCell: function(position) {
107395         var cell        = this.getCellByPosition(position),
107396             el          = this.el,
107397             adjustmentY = 0,
107398             adjustmentX = 0,
107399             elRegion    = el.getRegion(),
107400             panel       = this.ownerCt,
107401             cellRegion,
107402             record;
107403
107404         if (cell) {
107405             cellRegion = cell.getRegion();
107406             // cell is above
107407             if (cellRegion.top < elRegion.top) {
107408                 adjustmentY = cellRegion.top - elRegion.top;
107409             // cell is below
107410             } else if (cellRegion.bottom > elRegion.bottom) {
107411                 adjustmentY = cellRegion.bottom - elRegion.bottom;
107412             }
107413
107414             // cell is left
107415             if (cellRegion.left < elRegion.left) {
107416                 adjustmentX = cellRegion.left - elRegion.left;
107417             // cell is right
107418             } else if (cellRegion.right > elRegion.right) {
107419                 adjustmentX = cellRegion.right - elRegion.right;
107420             }
107421
107422             if (adjustmentY) {
107423                 // scroll the grid itself, so that all gridview's update.
107424                 panel.scrollByDeltaY(adjustmentY);
107425             }
107426             if (adjustmentX) {
107427                 panel.scrollByDeltaX(adjustmentX);
107428             }
107429             el.focus();
107430             this.fireEvent('cellfocus', record, cell, position);
107431         }
107432     },
107433
107434     /**
107435      * Scroll by delta. This affects this individual view ONLY and does not
107436      * synchronize across views or scrollers.
107437      * @param {Number} delta
107438      * @param {String} dir (optional) Valid values are scrollTop and scrollLeft. Defaults to scrollTop.
107439      * @private
107440      */
107441     scrollByDelta: function(delta, dir) {
107442         dir = dir || 'scrollTop';
107443         var elDom = this.el.dom;
107444         elDom[dir] = (elDom[dir] += delta);
107445     },
107446
107447     onUpdate: function(ds, index) {
107448         this.callParent(arguments);
107449     },
107450
107451     /**
107452      * Save the scrollState in a private variable.
107453      * Must be used in conjunction with restoreScrollState
107454      */
107455     saveScrollState: function() {
107456         var dom = this.el.dom,
107457             state = this.scrollState;
107458
107459         state.left = dom.scrollLeft;
107460         state.top = dom.scrollTop;
107461     },
107462
107463     /**
107464      * Restore the scrollState.
107465      * Must be used in conjunction with saveScrollState
107466      * @private
107467      */
107468     restoreScrollState: function() {
107469         var dom = this.el.dom,
107470             state = this.scrollState,
107471             headerEl = this.headerCt.el.dom;
107472
107473         headerEl.scrollLeft = dom.scrollLeft = state.left;
107474         dom.scrollTop = state.top;
107475     },
107476
107477     /**
107478      * Refresh the grid view.
107479      * Saves and restores the scroll state, generates a new template, stripes rows
107480      * and invalidates the scrollers.
107481      * @param {Boolean} firstPass This is a private flag for internal use only.
107482      */
107483     refresh: function(firstPass) {
107484         var me = this,
107485             table;
107486
107487         //this.saveScrollState();
107488         me.setNewTemplate();
107489         
107490         // The table.unselectable() call below adds a selectstart listener to the table element.
107491         // Before we clear the whole dataview in the callParent, we remove all the listeners from the
107492         // table. This prevents a big memory leak on IE6 and IE7.
107493         if (me.rendered) {
107494             table = me.el.child('table');
107495             if (table) {
107496                 table.removeAllListeners();
107497             }
107498         }
107499         
107500         me.callParent(arguments);
107501
107502         //this.restoreScrollState();
107503         if (me.rendered) {
107504             // Make the table view unselectable
107505             table = me.el.child('table');
107506             if (table) {
107507                 table.unselectable();
107508             }
107509             
107510             if (!firstPass) {
107511                 // give focus back to gridview
107512                 me.el.focus();
107513             }
107514         }
107515     },
107516
107517     processItemEvent: function(type, record, row, rowIndex, e) {
107518         var me = this,
107519             cell = e.getTarget(me.cellSelector, row),
107520             cellIndex = cell ? cell.cellIndex : -1,
107521             map = me.statics().EventMap,
107522             selModel = me.getSelectionModel(),
107523             result;
107524
107525         if (type == 'keydown' && !cell && selModel.getCurrentPosition) {
107526             // CellModel, otherwise we can't tell which cell to invoke
107527             cell = me.getCellByPosition(selModel.getCurrentPosition());
107528             if (cell) {
107529                 cell = cell.dom;
107530                 cellIndex = cell.cellIndex;
107531             }
107532         }
107533
107534         result = me.fireEvent('uievent', type, me, cell, rowIndex, cellIndex, e);
107535
107536         if (result === false || me.callParent(arguments) === false) {
107537             return false;
107538         }
107539
107540         // Don't handle cellmouseenter and cellmouseleave events for now
107541         if (type == 'mouseover' || type == 'mouseout') {
107542             return true;
107543         }
107544
107545         return !(
107546             // We are adding cell and feature events  
107547             (me['onBeforeCell' + map[type]](cell, cellIndex, record, row, rowIndex, e) === false) ||
107548             (me.fireEvent('beforecell' + type, me, cell, cellIndex, record, row, rowIndex, e) === false) ||
107549             (me['onCell' + map[type]](cell, cellIndex, record, row, rowIndex, e) === false) ||
107550             (me.fireEvent('cell' + type, me, cell, cellIndex, record, row, rowIndex, e) === false)
107551         );
107552     },
107553
107554     processSpecialEvent: function(e) {
107555         var me = this,
107556             map = this.statics().EventMap,
107557             features = this.features,
107558             ln = features.length,
107559             type = e.type,
107560             i, feature, prefix, featureTarget,
107561             beforeArgs, args,
107562             panel = me.ownerCt;
107563
107564         this.callParent(arguments);
107565
107566         if (type == 'mouseover' || type == 'mouseout') {
107567             return;
107568         }
107569
107570         for (i = 0; i < ln; i++) {
107571             feature = features[i];
107572             if (feature.hasFeatureEvent) {
107573                 featureTarget = e.getTarget(feature.eventSelector, me.getTargetEl());
107574                 if (featureTarget) {
107575                     prefix = feature.eventPrefix;
107576                     // allows features to implement getFireEventArgs to change the
107577                     // fireEvent signature
107578                     beforeArgs = feature.getFireEventArgs('before' + prefix + type, me, featureTarget);
107579                     args = feature.getFireEventArgs(prefix + type, me, featureTarget);
107580                     
107581                     if (
107582                         // before view event
107583                         (me.fireEvent.apply(me, beforeArgs) === false) ||
107584                         // panel grid event
107585                         (panel.fireEvent.apply(panel, beforeArgs) === false) ||
107586                         // view event
107587                         (me.fireEvent.apply(me, args) === false) ||
107588                         // panel event
107589                         (panel.fireEvent.apply(panel, args) === false)
107590                     ) {
107591                         return false;
107592                     }
107593                 }
107594             }
107595         }
107596         return true;
107597     },
107598
107599     onCellMouseDown: Ext.emptyFn,
107600     onCellMouseUp: Ext.emptyFn,
107601     onCellClick: Ext.emptyFn,
107602     onCellDblClick: Ext.emptyFn,
107603     onCellContextMenu: Ext.emptyFn,
107604     onCellKeyDown: Ext.emptyFn,
107605     onBeforeCellMouseDown: Ext.emptyFn,
107606     onBeforeCellMouseUp: Ext.emptyFn,
107607     onBeforeCellClick: Ext.emptyFn,
107608     onBeforeCellDblClick: Ext.emptyFn,
107609     onBeforeCellContextMenu: Ext.emptyFn,
107610     onBeforeCellKeyDown: Ext.emptyFn,
107611
107612     /**
107613      * Expand a particular header to fit the max content width.
107614      * This will ONLY expand, not contract.
107615      * @private
107616      */
107617     expandToFit: function(header) {
107618         var maxWidth = this.getMaxContentWidth(header);
107619         delete header.flex;
107620         header.setWidth(maxWidth);
107621     },
107622
107623     /**
107624      * Get the max contentWidth of the header's text and all cells
107625      * in the grid under this header.
107626      * @private
107627      */
107628     getMaxContentWidth: function(header) {
107629         var cellSelector = header.getCellInnerSelector(),
107630             cells        = this.el.query(cellSelector),
107631             i = 0,
107632             ln = cells.length,
107633             maxWidth = header.el.dom.scrollWidth,
107634             scrollWidth;
107635
107636         for (; i < ln; i++) {
107637             scrollWidth = cells[i].scrollWidth;
107638             if (scrollWidth > maxWidth) {
107639                 maxWidth = scrollWidth;
107640             }
107641         }
107642         return maxWidth;
107643     },
107644
107645     getPositionByEvent: function(e) {
107646         var cellNode = e.getTarget(this.cellSelector),
107647             rowNode  = e.getTarget(this.itemSelector),
107648             record   = this.getRecord(rowNode),
107649             header   = this.getHeaderByCell(cellNode);
107650
107651         return this.getPosition(record, header);
107652     },
107653
107654     getHeaderByCell: function(cell) {
107655         if (cell) {
107656             var m = cell.className.match(this.cellRe);
107657             if (m && m[1]) {
107658                 return Ext.getCmp(m[1]);
107659             }
107660         }
107661         return false;
107662     },
107663
107664     /**
107665      * @param {Object} position The current row and column: an object containing the following properties:<ul>
107666      * <li>row<div class="sub-desc"> The row <b>index</b></div></li>
107667      * <li>column<div class="sub-desc">The column <b>index</b></div></li>
107668      * </ul>
107669      * @param {String} direction 'up', 'down', 'right' and 'left'
107670      * @param {Ext.EventObject} e event
107671      * @param {Boolean} preventWrap Set to true to prevent wrap around to the next or previous row.
107672      * @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.
107673      * @param {Scope} scope Scope to run the verifierFn in
107674      * @returns {Object} newPosition An object containing the following properties:<ul>
107675      * <li>row<div class="sub-desc"> The row <b>index</b></div></li>
107676      * <li>column<div class="sub-desc">The column <b>index</b></div></li>
107677      * </ul>
107678      * @private
107679      */
107680     walkCells: function(pos, direction, e, preventWrap, verifierFn, scope) {
107681         var row      = pos.row,
107682             column   = pos.column,
107683             rowCount = this.store.getCount(),
107684             firstCol = this.getFirstVisibleColumnIndex(),
107685             lastCol  = this.getLastVisibleColumnIndex(),
107686             newPos   = {row: row, column: column},
107687             activeHeader = this.headerCt.getHeaderAtIndex(column);
107688
107689         // no active header or its currently hidden
107690         if (!activeHeader || activeHeader.hidden) {
107691             return false;
107692         }
107693
107694         e = e || {};
107695         direction = direction.toLowerCase();
107696         switch (direction) {
107697             case 'right':
107698                 // has the potential to wrap if its last
107699                 if (column === lastCol) {
107700                     // if bottom row and last column, deny right
107701                     if (preventWrap || row === rowCount - 1) {
107702                         return false;
107703                     }
107704                     if (!e.ctrlKey) {
107705                         // otherwise wrap to nextRow and firstCol
107706                         newPos.row = row + 1;
107707                         newPos.column = firstCol;
107708                     }
107709                 // go right
107710                 } else {
107711                     if (!e.ctrlKey) {
107712                         newPos.column = column + this.getRightGap(activeHeader);
107713                     } else {
107714                         newPos.column = lastCol;
107715                     }
107716                 }
107717                 break;
107718
107719             case 'left':
107720                 // has the potential to wrap
107721                 if (column === firstCol) {
107722                     // if top row and first column, deny left
107723                     if (preventWrap || row === 0) {
107724                         return false;
107725                     }
107726                     if (!e.ctrlKey) {
107727                         // otherwise wrap to prevRow and lastCol
107728                         newPos.row = row - 1;
107729                         newPos.column = lastCol;
107730                     }
107731                 // go left
107732                 } else {
107733                     if (!e.ctrlKey) {
107734                         newPos.column = column + this.getLeftGap(activeHeader);
107735                     } else {
107736                         newPos.column = firstCol;
107737                     }
107738                 }
107739                 break;
107740
107741             case 'up':
107742                 // if top row, deny up
107743                 if (row === 0) {
107744                     return false;
107745                 // go up
107746                 } else {
107747                     if (!e.ctrlKey) {
107748                         newPos.row = row - 1;
107749                     } else {
107750                         newPos.row = 0;
107751                     }
107752                 }
107753                 break;
107754
107755             case 'down':
107756                 // if bottom row, deny down
107757                 if (row === rowCount - 1) {
107758                     return false;
107759                 // go down
107760                 } else {
107761                     if (!e.ctrlKey) {
107762                         newPos.row = row + 1;
107763                     } else {
107764                         newPos.row = rowCount - 1;
107765                     }
107766                 }
107767                 break;
107768         }
107769
107770         if (verifierFn && verifierFn.call(scope || window, newPos) !== true) {
107771             return false;
107772         } else {
107773             return newPos;
107774         }
107775     },
107776     getFirstVisibleColumnIndex: function() {
107777         var headerCt   = this.getHeaderCt(),
107778             allColumns = headerCt.getGridColumns(),
107779             visHeaders = Ext.ComponentQuery.query(':not([hidden])', allColumns),
107780             firstHeader = visHeaders[0];
107781
107782         return headerCt.getHeaderIndex(firstHeader);
107783     },
107784
107785     getLastVisibleColumnIndex: function() {
107786         var headerCt   = this.getHeaderCt(),
107787             allColumns = headerCt.getGridColumns(),
107788             visHeaders = Ext.ComponentQuery.query(':not([hidden])', allColumns),
107789             lastHeader = visHeaders[visHeaders.length - 1];
107790
107791         return headerCt.getHeaderIndex(lastHeader);
107792     },
107793
107794     getHeaderCt: function() {
107795         return this.headerCt;
107796     },
107797
107798     getPosition: function(record, header) {
107799         var me = this,
107800             store = me.store,
107801             gridCols = me.headerCt.getGridColumns();
107802
107803         return {
107804             row: store.indexOf(record),
107805             column: Ext.Array.indexOf(gridCols, header)
107806         };
107807     },
107808
107809     /**
107810      * Determines the 'gap' between the closest adjacent header to the right
107811      * that is not hidden.
107812      * @private
107813      */
107814     getRightGap: function(activeHeader) {
107815         var headerCt        = this.getHeaderCt(),
107816             headers         = headerCt.getGridColumns(),
107817             activeHeaderIdx = Ext.Array.indexOf(headers, activeHeader),
107818             i               = activeHeaderIdx + 1,
107819             nextIdx;
107820
107821         for (; i <= headers.length; i++) {
107822             if (!headers[i].hidden) {
107823                 nextIdx = i;
107824                 break;
107825             }
107826         }
107827
107828         return nextIdx - activeHeaderIdx;
107829     },
107830
107831     beforeDestroy: function() {
107832         if (this.rendered) {
107833             table = this.el.child('table');
107834             if (table) {
107835                 table.removeAllListeners();
107836             }
107837         }
107838         this.callParent(arguments);
107839     },
107840
107841     /**
107842      * Determines the 'gap' between the closest adjacent header to the left
107843      * that is not hidden.
107844      * @private
107845      */
107846     getLeftGap: function(activeHeader) {
107847         var headerCt        = this.getHeaderCt(),
107848             headers         = headerCt.getGridColumns(),
107849             activeHeaderIdx = Ext.Array.indexOf(headers, activeHeader),
107850             i               = activeHeaderIdx - 1,
107851             prevIdx;
107852
107853         for (; i >= 0; i--) {
107854             if (!headers[i].hidden) {
107855                 prevIdx = i;
107856                 break;
107857             }
107858         }
107859
107860         return prevIdx - activeHeaderIdx;
107861     }
107862 });
107863 /**
107864  * @class Ext.grid.View
107865  * @extends Ext.view.Table
107866
107867 The grid View class provides extra {@link Ext.grid.Panel} specific functionality to the
107868 {@link Ext.view.Table}. In general, this class is not instanced directly, instead a viewConfig
107869 option is passed to the grid:
107870
107871     Ext.create('Ext.grid.Panel', {
107872         // other options
107873         viewConfig: {
107874             stripeRows: false
107875         }
107876     });
107877     
107878 __Drag Drop__
107879 Drag and drop functionality can be achieved in the grid by attaching a {@link Ext.grid.plugin.DragDrop} plugin
107880 when creating the view.
107881
107882     Ext.create('Ext.grid.Panel', {
107883         // other options
107884         viewConfig: {
107885             plugins: {
107886                 ddGroup: 'people-group',
107887                 ptype: 'gridviewdragdrop',
107888                 enableDrop: false
107889             }
107890         }
107891     });
107892
107893  * @markdown
107894  */
107895 Ext.define('Ext.grid.View', {
107896     extend: 'Ext.view.Table',
107897     alias: 'widget.gridview',
107898
107899     /**
107900      * @cfg {Boolean} stripeRows <tt>true</tt> to stripe the rows. Default is <tt>false</tt>.
107901      * <p>This causes the CSS class <tt><b>x-grid-row-alt</b></tt> to be added to alternate rows of
107902      * the grid. A default CSS rule is provided which sets a background color, but you can override this
107903      * with a rule which either overrides the <b>background-color</b> style using the '!important'
107904      * modifier, or which uses a CSS selector of higher specificity.</p>
107905      */
107906     stripeRows: true,
107907     
107908     invalidateScrollerOnRefresh: true,
107909     
107910     /**
107911      * Scroll the GridView to the top by scrolling the scroller.
107912      * @private
107913      */
107914     scrollToTop : function(){
107915         if (this.rendered) {
107916             var section = this.ownerCt,
107917                 verticalScroller = section.verticalScroller;
107918                 
107919             if (verticalScroller) {
107920                 verticalScroller.scrollToTop();
107921             }
107922         }
107923     },
107924
107925     // after adding a row stripe rows from then on
107926     onAdd: function(ds, records, index) {
107927         this.callParent(arguments);
107928         this.doStripeRows(index);
107929     },
107930     
107931     // after removing a row stripe rows from then on
107932     onRemove: function(ds, records, index) {
107933         this.callParent(arguments);
107934         this.doStripeRows(index);
107935     },
107936     
107937     /**
107938      * Stripe rows from a particular row index
107939      * @param {Number} startRow
107940      * @private
107941      */
107942     doStripeRows: function(startRow) {
107943         // ensure stripeRows configuration is turned on
107944         if (this.stripeRows) {
107945             var rows   = this.getNodes(startRow),
107946                 rowsLn = rows.length,
107947                 i      = 0,
107948                 row;
107949                 
107950             for (; i < rowsLn; i++) {
107951                 row = rows[i];
107952                 // Remove prior applied row classes.
107953                 row.className = row.className.replace(this.rowClsRe, ' ');
107954                 // Every odd row will get an additional cls
107955                 if (i % 2 === 1) {
107956                     row.className += (' ' + this.altRowCls);
107957                 }
107958             }
107959         }
107960     },
107961     
107962     refresh: function(firstPass) {
107963         this.callParent(arguments);
107964         this.doStripeRows(0);
107965         // TODO: Remove gridpanel dependency
107966         var g = this.up('gridpanel');
107967         if (g && this.invalidateScrollerOnRefresh) {
107968             g.invalidateScroller();
107969         }
107970     }
107971 });
107972
107973 /**
107974  * @author Aaron Conran
107975  * @class Ext.grid.Panel
107976  * @extends Ext.panel.Table
107977  *
107978  * Grids are an excellent way of showing large amounts of tabular data on the client side. Essentially a supercharged 
107979  * `<table>`, GridPanel makes it easy to fetch, sort and filter large amounts of data.
107980  * 
107981  * Grids are composed of 2 main pieces - a {@link Ext.data.Store Store} full of data and a set of columns to render.
107982  *
107983  * {@img Ext.grid.Panel/Ext.grid.Panel1.png Ext.grid.Panel component}
107984  *
107985  * ## Basic GridPanel
107986  *
107987  *     Ext.create('Ext.data.Store', {
107988  *         storeId:'simpsonsStore',
107989  *         fields:['name', 'email', 'phone'],
107990  *         data:{'items':[
107991  *             {"name":"Lisa", "email":"lisa@simpsons.com", "phone":"555-111-1224"},
107992  *             {"name":"Bart", "email":"bart@simpsons.com", "phone":"555--222-1234"},
107993  *             {"name":"Homer", "email":"home@simpsons.com", "phone":"555-222-1244"},                        
107994  *             {"name":"Marge", "email":"marge@simpsons.com", "phone":"555-222-1254"}            
107995  *         ]},
107996  *         proxy: {
107997  *             type: 'memory',
107998  *             reader: {
107999  *                 type: 'json',
108000  *                 root: 'items'
108001  *             }
108002  *         }
108003  *     });
108004  *     
108005  *     Ext.create('Ext.grid.Panel', {
108006  *         title: 'Simpsons',
108007  *         store: Ext.data.StoreManager.lookup('simpsonsStore'),
108008  *         columns: [
108009  *             {header: 'Name',  dataIndex: 'name'},
108010  *             {header: 'Email', dataIndex: 'email', flex:1},
108011  *             {header: 'Phone', dataIndex: 'phone'}
108012  *         ],
108013  *         height: 200,
108014  *         width: 400,
108015  *         renderTo: Ext.getBody()
108016  *     });
108017  * 
108018  * The code above produces a simple grid with three columns. We specified a Store which will load JSON data inline. 
108019  * In most apps we would be placing the grid inside another container and wouldn't need to use the
108020  * {@link #height}, {@link #width} and {@link #renderTo} configurations but they are included here to make it easy to get
108021  * up and running.
108022  * 
108023  * The grid we created above will contain a header bar with a title ('Simpsons'), a row of column headers directly underneath
108024  * and finally the grid rows under the headers.
108025  * 
108026  * ## Configuring columns
108027  * 
108028  * By default, each column is sortable and will toggle between ASC and DESC sorting when you click on its header. Each
108029  * column header is also reorderable by default, and each gains a drop-down menu with options to hide and show columns.
108030  * It's easy to configure each column - here we use the same example as above and just modify the columns config:
108031  * 
108032  *     columns: [
108033  *         {
108034  *             header: 'Name',
108035  *             dataIndex: 'name',
108036  *             sortable: false,
108037  *             hideable: false,
108038  *             flex: 1
108039  *         },
108040  *         {
108041  *             header: 'Email',
108042  *             dataIndex: 'email',
108043  *             hidden: true
108044  *         },
108045  *         {
108046  *             header: 'Phone',
108047  *             dataIndex: 'phone',
108048  *             width: 100
108049  *         }
108050  *     ]
108051  * 
108052  * We turned off sorting and hiding on the 'Name' column so clicking its header now has no effect. We also made the Email
108053  * column hidden by default (it can be shown again by using the menu on any other column). We also set the Phone column to
108054  * a fixed with of 100px and flexed the Name column, which means it takes up all remaining width after the other columns 
108055  * have been accounted for. See the {@link Ext.grid.column.Column column docs} for more details.
108056  * 
108057  * ## Renderers
108058  * 
108059  * As well as customizing columns, it's easy to alter the rendering of individual cells using renderers. A renderer is 
108060  * tied to a particular column and is passed the value that would be rendered into each cell in that column. For example,
108061  * we could define a renderer function for the email column to turn each email address into a mailto link:
108062  * 
108063  *     columns: [
108064  *         {
108065  *             header: 'Email',
108066  *             dataIndex: 'email',
108067  *             renderer: function(value) {
108068  *                 return Ext.String.format('<a href="mailto:{0}">{1}</a>', value, value);
108069  *             }
108070  *         }
108071  *     ]
108072  * 
108073  * See the {@link Ext.grid.column.Column column docs} for more information on renderers.
108074  * 
108075  * ## Selection Models
108076  * 
108077  * Sometimes all you want is to render data onto the screen for viewing, but usually it's necessary to interact with or 
108078  * update that data. Grids use a concept called a Selection Model, which is simply a mechanism for selecting some part of
108079  * the data in the grid. The two main types of Selection Model are RowSelectionModel, where entire rows are selected, and
108080  * CellSelectionModel, where individual cells are selected.
108081  * 
108082  * Grids use a Row Selection Model by default, but this is easy to customise like so:
108083  * 
108084  *     Ext.create('Ext.grid.Panel', {
108085  *         selType: 'cellmodel',
108086  *         store: ...
108087  *     });
108088  * 
108089  * Specifying the `cellmodel` changes a couple of things. Firstly, clicking on a cell now
108090  * selects just that cell (using a {@link Ext.selection.RowModel rowmodel} will select the entire row), and secondly the
108091  * keyboard navigation will walk from cell to cell instead of row to row. Cell-based selection models are usually used in
108092  * conjunction with editing.
108093  * 
108094  * {@img Ext.grid.Panel/Ext.grid.Panel2.png Ext.grid.Panel cell editing}
108095  *
108096  * ## Editing
108097  * 
108098  * Grid has built-in support for in-line editing. There are two chief editing modes - cell editing and row editing. Cell
108099  * editing is easy to add to your existing column setup - here we'll just modify the example above to include an editor
108100  * on both the name and the email columns:
108101  * 
108102  *     Ext.create('Ext.grid.Panel', {
108103  *         title: 'Simpsons',
108104  *         store: Ext.data.StoreManager.lookup('simpsonsStore'),
108105  *         columns: [
108106  *             {header: 'Name',  dataIndex: 'name', field: 'textfield'},
108107  *             {header: 'Email', dataIndex: 'email', flex:1, 
108108  *                 field:{
108109  *                     xtype:'textfield',
108110  *                     allowBlank:false
108111  *                 }
108112  *             },
108113  *             {header: 'Phone', dataIndex: 'phone'}
108114  *         ],
108115  *         selType: 'cellmodel',
108116  *         plugins: [
108117  *             Ext.create('Ext.grid.plugin.CellEditing', {
108118  *                 clicksToEdit: 1
108119  *             })
108120  *         ],
108121  *         height: 200,
108122  *         width: 400,
108123  *         renderTo: Ext.getBody()
108124  *     });
108125  * 
108126  * This requires a little explanation. We're passing in {@link #store store} and {@link #columns columns} as normal, but 
108127  * this time we've also specified a {@link #field field} on two of our columns. For the Name column we just want a default
108128  * textfield to edit the value, so we specify 'textfield'. For the Email column we customized the editor slightly by 
108129  * passing allowBlank: false, which will provide inline validation.
108130  * 
108131  * To support cell editing, we also specified that the grid should use the 'cellmodel' {@link #selType}, and created an
108132  * instance of the {@link Ext.grid.plugin.CellEditing CellEditing plugin}, which we configured to activate each editor after a
108133  * single click.
108134  * 
108135  * {@img Ext.grid.Panel/Ext.grid.Panel3.png Ext.grid.Panel row editing}
108136  *
108137  * ## Row Editing
108138  * 
108139  * The other type of editing is row-based editing, using the RowEditor component. This enables you to edit an entire row
108140  * at a time, rather than editing cell by cell. Row Editing works in exactly the same way as cell editing, all we need to
108141  * do is change the plugin type to {@link Ext.grid.plugin.RowEditing}, and set the selType to 'rowmodel':
108142  * 
108143  *     Ext.create('Ext.grid.Panel', {
108144  *         title: 'Simpsons',
108145  *         store: Ext.data.StoreManager.lookup('simpsonsStore'),
108146  *         columns: [
108147  *             {header: 'Name',  dataIndex: 'name', field: 'textfield'},
108148  *             {header: 'Email', dataIndex: 'email', flex:1, 
108149  *                 field:{
108150  *                     xtype:'textfield',
108151  *                     allowBlank:false
108152  *                 }
108153  *             },
108154  *             {header: 'Phone', dataIndex: 'phone'}
108155  *         ],
108156  *         selType: 'rowmodel',
108157  *         plugins: [
108158  *             Ext.create('Ext.grid.plugin.RowEditing', {
108159  *                 clicksToEdit: 1
108160  *             })
108161  *         ],
108162  *         height: 200,
108163  *         width: 400,
108164  *         renderTo: Ext.getBody()
108165  *     });
108166  * 
108167  * Again we passed some configuration to our {@link Ext.grid.plugin.RowEditing} plugin, and now when we click each row a row
108168  * editor will appear and enable us to edit each of the columns we have specified an editor for.
108169  * 
108170  * ## Sorting & Filtering
108171  * 
108172  * Every grid is attached to a {@link Ext.data.Store Store}, which provides multi-sort and filtering capabilities. It's
108173  * easy to set up a grid to be sorted from the start:
108174  * 
108175  *     var myGrid = Ext.create('Ext.grid.Panel', {
108176  *         store: {
108177  *             fields: ['name', 'email', 'phone'],
108178  *             sorters: ['name', 'phone']
108179  *         },
108180  *         columns: [
108181  *             {text: 'Name',  dataIndex: 'name'},
108182  *             {text: 'Email', dataIndex: 'email'}
108183  *         ]
108184  *     });
108185  * 
108186  * Sorting at run time is easily accomplished by simply clicking each column header. If you need to perform sorting on 
108187  * more than one field at run time it's easy to do so by adding new sorters to the store:
108188  * 
108189  *     myGrid.store.sort([
108190  *         {property: 'name',  direction: 'ASC'},
108191  *         {property: 'email', direction: 'DESC'},
108192  *     ]);
108193  * 
108194  * {@img Ext.grid.Panel/Ext.grid.Panel4.png Ext.grid.Panel grouping}
108195  * 
108196  * ## Grouping
108197  * 
108198  * Grid supports the grouping of rows by any field. For example if we had a set of employee records, we might want to 
108199  * group by the department that each employee works in. Here's how we might set that up:
108200  * 
108201  *     var store = Ext.create('Ext.data.Store', {
108202  *         storeId:'employeeStore',
108203  *         fields:['name', 'senority', 'department'],
108204  *         groupField: 'department',
108205  *         data:{'employees':[
108206  *             {"name":"Michael Scott", "senority":7, "department":"Manangement"},
108207  *             {"name":"Dwight Schrute", "senority":2, "department":"Sales"},
108208  *             {"name":"Jim Halpert", "senority":3, "department":"Sales"},
108209  *             {"name":"Kevin Malone", "senority":4, "department":"Accounting"},
108210  *             {"name":"Angela Martin", "senority":5, "department":"Accounting"}                        
108211  *         ]},
108212  *         proxy: {
108213  *             type: 'memory',
108214  *             reader: {
108215  *                 type: 'json',
108216  *                 root: 'employees'
108217  *             }
108218  *         }
108219  *     });
108220  *     
108221  *     Ext.create('Ext.grid.Panel', {
108222  *         title: 'Employees',
108223  *         store: Ext.data.StoreManager.lookup('employeeStore'),
108224  *         columns: [
108225  *             {header: 'Name',  dataIndex: 'name'},
108226  *             {header: 'Senority', dataIndex: 'senority'}
108227  *         ],        
108228  *         features: [{ftype:'grouping'}],
108229  *         width: 200,
108230  *         height: 275,
108231  *         renderTo: Ext.getBody()
108232  *     });
108233  * 
108234  * ## Infinite Scrolling
108235  *
108236  * Grid supports infinite scrolling as an alternative to using a paging toolbar. Your users can scroll through thousands
108237  * of records without the performance penalties of renderering all the records on screen at once. The grid should be bound
108238  * to a store with a pageSize specified.
108239  *
108240  *     var grid = Ext.create('Ext.grid.Panel', {
108241  *         // Use a PagingGridScroller (this is interchangeable with a PagingToolbar)
108242  *         verticalScrollerType: 'paginggridscroller',
108243  *         // do not reset the scrollbar when the view refreshs
108244  *         invalidateScrollerOnRefresh: false,
108245  *         // infinite scrolling does not support selection
108246  *         disableSelection: true,
108247  *         // ...
108248  *     });
108249  * 
108250  * ## Paging
108251  *
108252  * Grid supports paging through large sets of data via a PagingToolbar or PagingGridScroller (see the Infinite Scrolling section above).
108253  * To leverage paging via a toolbar or scroller, you need to set a pageSize configuration on the Store.
108254  *
108255  *     var itemsPerPage = 2;   // set the number of items you want per page
108256  *     
108257  *     var store = Ext.create('Ext.data.Store', {
108258  *         id:'simpsonsStore',
108259  *         autoLoad: false,
108260  *         fields:['name', 'email', 'phone'],
108261  *         pageSize: itemsPerPage, // items per page
108262  *         proxy: {
108263  *             type: 'ajax',
108264  *             url: 'pagingstore.js',  // url that will load data with respect to start and limit params
108265  *             reader: {
108266  *                 type: 'json',
108267  *                 root: 'items',
108268  *                 totalProperty: 'total'
108269  *             }
108270  *         }
108271  *     });
108272  *     
108273  *     // specify segment of data you want to load using params
108274  *     store.load({
108275  *         params:{
108276  *             start:0,    
108277  *             limit: itemsPerPage
108278  *         }
108279  *     });
108280  *     
108281  *     Ext.create('Ext.grid.Panel', {
108282  *         title: 'Simpsons',
108283  *         store: store,
108284  *         columns: [
108285  *             {header: 'Name',  dataIndex: 'name'},
108286  *             {header: 'Email', dataIndex: 'email', flex:1},
108287  *             {header: 'Phone', dataIndex: 'phone'}
108288  *         ],
108289  *         width: 400,
108290  *         height: 125,
108291  *         dockedItems: [{
108292  *             xtype: 'pagingtoolbar',
108293  *             store: store,   // same store GridPanel is using
108294  *             dock: 'bottom',
108295  *             displayInfo: true
108296  *         }],
108297  *         renderTo: Ext.getBody()
108298  *     }); 
108299  * 
108300  * {@img Ext.grid.Panel/Ext.grid.Panel5.png Ext.grid.Panel grouping}
108301  * 
108302  * @docauthor Ed Spencer
108303  */
108304 Ext.define('Ext.grid.Panel', {
108305     extend: 'Ext.panel.Table',
108306     requires: ['Ext.grid.View'],
108307     alias: ['widget.gridpanel', 'widget.grid'],
108308     alternateClassName: ['Ext.list.ListView', 'Ext.ListView', 'Ext.grid.GridPanel'],
108309     viewType: 'gridview',
108310     
108311     lockable: false,
108312     
108313     // Required for the Lockable Mixin. These are the configurations which will be copied to the
108314     // normal and locked sub tablepanels
108315     normalCfgCopy: ['invalidateScrollerOnRefresh', 'verticalScroller', 'verticalScrollDock', 'verticalScrollerType', 'scroll'],
108316     lockedCfgCopy: ['invalidateScrollerOnRefresh'],
108317     
108318     /**
108319      * @cfg {Boolean} columnLines Adds column line styling
108320      */
108321     
108322     initComponent: function() {
108323         var me = this;
108324
108325         if (me.columnLines) {
108326             me.setColumnLines(me.columnLines);
108327         }
108328         
108329         me.callParent();
108330     },
108331     
108332     setColumnLines: function(show) {
108333         var me = this,
108334             method = (show) ? 'addClsWithUI' : 'removeClsWithUI';
108335         
108336         me[method]('with-col-lines')
108337     }
108338 });
108339 // Currently has the following issues:
108340 // - Does not handle postEditValue
108341 // - Fields without editors need to sync with their values in Store
108342 // - starting to edit another record while already editing and dirty should probably prevent it
108343 // - aggregating validation messages
108344 // - tabIndex is not managed bc we leave elements in dom, and simply move via positioning
108345 // - layout issues when changing sizes/width while hidden (layout bug)
108346
108347 /**
108348  * @class Ext.grid.RowEditor
108349  * @extends Ext.form.Panel
108350  *
108351  * Internal utility class used to provide row editing functionality. For developers, they should use
108352  * the RowEditing plugin to use this functionality with a grid.
108353  *
108354  * @ignore
108355  */
108356 Ext.define('Ext.grid.RowEditor', {
108357     extend: 'Ext.form.Panel',
108358     requires: [
108359         'Ext.tip.ToolTip',
108360         'Ext.util.HashMap',
108361         'Ext.util.KeyNav'
108362     ],
108363
108364     saveBtnText  : 'Update',
108365     cancelBtnText: 'Cancel',
108366     errorsText: 'Errors',
108367     dirtyText: 'You need to commit or cancel your changes',
108368
108369     lastScrollLeft: 0,
108370     lastScrollTop: 0,
108371
108372     border: false,
108373
108374     initComponent: function() {
108375         var me = this,
108376             form;
108377
108378         me.cls = Ext.baseCSSPrefix + 'grid-row-editor';
108379
108380         me.layout = {
108381             type: 'hbox',
108382             align: 'middle'
108383         };
108384
108385         // Maintain field-to-column mapping
108386         // It's easy to get a field from a column, but not vice versa
108387         me.columns = Ext.create('Ext.util.HashMap');
108388         me.columns.getKey = function(columnHeader) {
108389             var f;
108390             if (columnHeader.getEditor) {
108391                 f = columnHeader.getEditor();
108392                 if (f) {
108393                     return f.id;
108394                 }
108395             }
108396             return columnHeader.id;
108397         };
108398         me.mon(me.columns, {
108399             add: me.onFieldAdd,
108400             remove: me.onFieldRemove,
108401             replace: me.onFieldReplace,
108402             scope: me
108403         });
108404
108405         me.callParent(arguments);
108406
108407         if (me.fields) {
108408             me.setField(me.fields);
108409             delete me.fields;
108410         }
108411
108412         form = me.getForm();
108413         form.trackResetOnLoad = true;
108414     },
108415
108416     onFieldChange: function() {
108417         var me = this,
108418             form = me.getForm(),
108419             valid = form.isValid();
108420         if (me.errorSummary && me.isVisible()) {
108421             me[valid ? 'hideToolTip' : 'showToolTip']();
108422         }
108423         if (me.floatingButtons) {
108424             me.floatingButtons.child('#update').setDisabled(!valid);
108425         }
108426         me.isValid = valid;
108427     },
108428
108429     afterRender: function() {
108430         var me = this,
108431             plugin = me.editingPlugin;
108432
108433         me.callParent(arguments);
108434         me.mon(me.renderTo, 'scroll', me.onCtScroll, me, { buffer: 100 });
108435
108436         // Prevent from bubbling click events to the grid view
108437         me.mon(me.el, {
108438             click: Ext.emptyFn,
108439             stopPropagation: true
108440         });
108441
108442         me.el.swallowEvent([
108443             'keypress',
108444             'keydown'
108445         ]);
108446
108447         me.keyNav = Ext.create('Ext.util.KeyNav', me.el, {
108448             enter: plugin.completeEdit,
108449             esc: plugin.onEscKey,
108450             scope: plugin
108451         });
108452
108453         me.mon(plugin.view, {
108454             beforerefresh: me.onBeforeViewRefresh,
108455             refresh: me.onViewRefresh,
108456             scope: me
108457         });
108458     },
108459
108460     onBeforeViewRefresh: function(view) {
108461         var me = this,
108462             viewDom = view.el.dom;
108463
108464         if (me.el.dom.parentNode === viewDom) {
108465             viewDom.removeChild(me.el.dom);
108466         }
108467     },
108468
108469     onViewRefresh: function(view) {
108470         var me = this,
108471             viewDom = view.el.dom,
108472             context = me.context,
108473             idx;
108474
108475         viewDom.appendChild(me.el.dom);
108476
108477         // Recover our row node after a view refresh
108478         if (context && (idx = context.store.indexOf(context.record)) >= 0) {
108479             context.row = view.getNode(idx);
108480             me.reposition();
108481             if (me.tooltip && me.tooltip.isVisible()) {
108482                 me.tooltip.setTarget(context.row);
108483             }
108484         } else {
108485             me.editingPlugin.cancelEdit();
108486         }
108487     },
108488
108489     onCtScroll: function(e, target) {
108490         var me = this,
108491             scrollTop  = target.scrollTop,
108492             scrollLeft = target.scrollLeft;
108493
108494         if (scrollTop !== me.lastScrollTop) {
108495             me.lastScrollTop = scrollTop;
108496             if ((me.tooltip && me.tooltip.isVisible()) || me.hiddenTip) {
108497                 me.repositionTip();
108498             }
108499         }
108500         if (scrollLeft !== me.lastScrollLeft) {
108501             me.lastScrollLeft = scrollLeft;
108502             me.reposition();
108503         }
108504     },
108505
108506     onColumnAdd: function(column) {
108507         this.setField(column);
108508     },
108509
108510     onColumnRemove: function(column) {
108511         this.columns.remove(column);
108512     },
108513
108514     onColumnResize: function(column, width) {
108515         column.getEditor().setWidth(width - 2);
108516         if (this.isVisible()) {
108517             this.reposition();
108518         }
108519     },
108520
108521     onColumnHide: function(column) {
108522         column.getEditor().hide();
108523         if (this.isVisible()) {
108524             this.reposition();
108525         }
108526     },
108527
108528     onColumnShow: function(column) {
108529         var field = column.getEditor();
108530         field.setWidth(column.getWidth() - 2).show();
108531         if (this.isVisible()) {
108532             this.reposition();
108533         }
108534     },
108535
108536     onColumnMove: function(column, fromIdx, toIdx) {
108537         var field = column.getEditor();
108538         if (this.items.indexOf(field) != toIdx) {
108539             this.move(fromIdx, toIdx);
108540         }
108541     },
108542
108543     onFieldAdd: function(hm, fieldId, column) {
108544         var me = this,
108545             colIdx = me.editingPlugin.grid.headerCt.getHeaderIndex(column),
108546             field = column.getEditor({ xtype: 'displayfield' });
108547
108548         me.insert(colIdx, field);
108549     },
108550
108551     onFieldRemove: function(hm, fieldId, column) {
108552         var me = this,
108553             field = column.getEditor(),
108554             fieldDom = field.el.dom;
108555         me.remove(field, false);
108556         fieldDom.parentNode.removeChild(fieldDom);
108557     },
108558
108559     onFieldReplace: function(hm, fieldId, column, oldColumn) {
108560         var me = this;
108561         me.onFieldRemove(hm, fieldId, oldColumn);
108562     },
108563
108564     clearFields: function() {
108565         var me = this,
108566             hm = me.columns;
108567         hm.each(function(fieldId) {
108568             hm.removeAtKey(fieldId);
108569         });
108570     },
108571
108572     getFloatingButtons: function() {
108573         var me = this,
108574             cssPrefix = Ext.baseCSSPrefix,
108575             btnsCss = cssPrefix + 'grid-row-editor-buttons',
108576             plugin = me.editingPlugin,
108577             btns;
108578
108579         if (!me.floatingButtons) {
108580             btns = me.floatingButtons = Ext.create('Ext.Container', {
108581                 renderTpl: [
108582                     '<div class="{baseCls}-ml"></div>',
108583                     '<div class="{baseCls}-mr"></div>',
108584                     '<div class="{baseCls}-bl"></div>',
108585                     '<div class="{baseCls}-br"></div>',
108586                     '<div class="{baseCls}-bc"></div>'
108587                 ],
108588
108589                 renderTo: me.el,
108590                 baseCls: btnsCss,
108591                 layout: {
108592                     type: 'hbox',
108593                     align: 'middle'
108594                 },
108595                 defaults: {
108596                     margins: '0 1 0 1'
108597                 },
108598                 items: [{
108599                     itemId: 'update',
108600                     flex: 1,
108601                     xtype: 'button',
108602                     handler: plugin.completeEdit,
108603                     scope: plugin,
108604                     text: me.saveBtnText,
108605                     disabled: !me.isValid
108606                 }, {
108607                     flex: 1,
108608                     xtype: 'button',
108609                     handler: plugin.cancelEdit,
108610                     scope: plugin,
108611                     text: me.cancelBtnText
108612                 }]
108613             });
108614
108615             // Prevent from bubbling click events to the grid view
108616             me.mon(btns.el, {
108617                 // BrowserBug: Opera 11.01
108618                 //   causes the view to scroll when a button is focused from mousedown
108619                 mousedown: Ext.emptyFn,
108620                 click: Ext.emptyFn,
108621                 stopEvent: true
108622             });
108623         }
108624         return me.floatingButtons;
108625     },
108626
108627     reposition: function(animateConfig) {
108628         var me = this,
108629             context = me.context,
108630             row = context && Ext.get(context.row),
108631             btns = me.getFloatingButtons(),
108632             btnEl = btns.el,
108633             grid = me.editingPlugin.grid,
108634             viewEl = grid.view.el,
108635             scroller = grid.verticalScroller,
108636
108637             // always get data from ColumnModel as its what drives
108638             // the GridView's sizing
108639             mainBodyWidth = grid.headerCt.getFullWidth(),
108640             scrollerWidth = grid.getWidth(),
108641
108642             // use the minimum as the columns may not fill up the entire grid
108643             // width
108644             width = Math.min(mainBodyWidth, scrollerWidth),
108645             scrollLeft = grid.view.el.dom.scrollLeft,
108646             btnWidth = btns.getWidth(),
108647             left = (width - btnWidth) / 2 + scrollLeft,
108648             y, rowH, newHeight,
108649
108650             invalidateScroller = function() {
108651                 if (scroller) {
108652                     scroller.invalidate();
108653                     btnEl.scrollIntoView(viewEl, false);
108654                 }
108655                 if (animateConfig && animateConfig.callback) {
108656                     animateConfig.callback.call(animateConfig.scope || me);
108657                 }
108658             };
108659
108660         // need to set both top/left
108661         if (row && Ext.isElement(row.dom)) {
108662             // Bring our row into view if necessary, so a row editor that's already
108663             // visible and animated to the row will appear smooth
108664             row.scrollIntoView(viewEl, false);
108665
108666             // Get the y position of the row relative to its top-most static parent.
108667             // offsetTop will be relative to the table, and is incorrect
108668             // when mixed with certain grid features (e.g., grouping).
108669             y = row.getXY()[1] - 5;
108670             rowH = row.getHeight();
108671             newHeight = rowH + 10;
108672
108673             // IE doesn't set the height quite right.
108674             // This isn't a border-box issue, it even happens
108675             // in IE8 and IE7 quirks.
108676             // TODO: Test in IE9!
108677             if (Ext.isIE) {
108678                 newHeight += 2;
108679             }
108680
108681             // Set editor height to match the row height
108682             if (me.getHeight() != newHeight) {
108683                 me.setHeight(newHeight);
108684                 me.el.setLeft(0);
108685             }
108686
108687             if (animateConfig) {
108688                 var animObj = {
108689                     to: {
108690                         y: y
108691                     },
108692                     duration: animateConfig.duration || 125,
108693                     listeners: {
108694                         afteranimate: function() {
108695                             invalidateScroller();
108696                             y = row.getXY()[1] - 5;
108697                             me.el.setY(y);
108698                         }
108699                     }
108700                 };
108701                 me.animate(animObj);
108702             } else {
108703                 me.el.setY(y);
108704                 invalidateScroller();
108705             }
108706         }
108707         if (me.getWidth() != mainBodyWidth) {
108708             me.setWidth(mainBodyWidth);
108709         }
108710         btnEl.setLeft(left);
108711     },
108712
108713     getEditor: function(fieldInfo) {
108714         var me = this;
108715
108716         if (Ext.isNumber(fieldInfo)) {
108717             // Query only form fields. This just future-proofs us in case we add
108718             // other components to RowEditor later on.  Don't want to mess with
108719             // indices.
108720             return me.query('>[isFormField]')[fieldInfo];
108721         } else if (fieldInfo instanceof Ext.grid.column.Column) {
108722             return fieldInfo.getEditor();
108723         }
108724     },
108725
108726     removeField: function(field) {
108727         var me = this;
108728
108729         // Incase we pass a column instead, which is fine
108730         field = me.getEditor(field);
108731         me.mun(field, 'validitychange', me.onValidityChange, me);
108732
108733         // Remove field/column from our mapping, which will fire the event to
108734         // remove the field from our container
108735         me.columns.removeKey(field.id);
108736     },
108737
108738     setField: function(column) {
108739         var me = this,
108740             field;
108741
108742         if (Ext.isArray(column)) {
108743             Ext.Array.forEach(column, me.setField, me);
108744             return;
108745         }
108746
108747         // Get a default display field if necessary
108748         field = column.getEditor(null, { xtype: 'displayfield' });
108749         field.margins = '0 0 0 2';
108750         field.setWidth(column.getWidth() - 2);
108751         me.mon(field, 'change', me.onFieldChange, me);
108752
108753         // Maintain mapping of fields-to-columns
108754         // This will fire events that maintain our container items
108755         me.columns.add(field.id, column);
108756     },
108757
108758     loadRecord: function(record) {
108759         var me = this,
108760             form = me.getForm();
108761         form.loadRecord(record);
108762         if (form.isValid()) {
108763             me.hideToolTip();
108764         } else {
108765             me.showToolTip();
108766         }
108767
108768         // render display fields so they honor the column renderer/template
108769         Ext.Array.forEach(me.query('>displayfield'), function(field) {
108770             me.renderColumnData(field, record);
108771         }, me);
108772     },
108773
108774     renderColumnData: function(field, record) {
108775         var me = this,
108776             grid = me.editingPlugin.grid,
108777             headerCt = grid.headerCt,
108778             view = grid.view,
108779             store = view.store,
108780             column = me.columns.get(field.id),
108781             value = field.getRawValue();
108782
108783         // honor our column's renderer (TemplateHeader sets renderer for us!)
108784         if (column.renderer) {
108785             var metaData = { tdCls: '', style: '' },
108786                 rowIdx = store.indexOf(record),
108787                 colIdx = headerCt.getHeaderIndex(column);
108788
108789             value = column.renderer.call(
108790                 column.scope || headerCt.ownerCt,
108791                 value,
108792                 metaData,
108793                 record,
108794                 rowIdx,
108795                 colIdx,
108796                 store,
108797                 view
108798             );
108799         }
108800
108801         field.setRawValue(value);
108802         field.resetOriginalValue();
108803     },
108804
108805     beforeEdit: function() {
108806         var me = this;
108807
108808         if (me.isVisible() && !me.autoCancel && me.isDirty()) {
108809             me.showToolTip();
108810             return false;
108811         }
108812     },
108813
108814     /**
108815      * Start editing the specified grid at the specified position.
108816      * @param {Model} record The Store data record which backs the row to be edited.
108817      * @param {Model} columnHeader The Column object defining the column to be edited.
108818      */
108819     startEdit: function(record, columnHeader) {
108820         var me = this,
108821             grid = me.editingPlugin.grid,
108822             view = grid.getView(),
108823             store = grid.store,
108824             context = me.context = Ext.apply(me.editingPlugin.context, {
108825                 view: grid.getView(),
108826                 store: store
108827             });
108828
108829         // make sure our row is selected before editing
108830         context.grid.getSelectionModel().select(record);
108831
108832         // Reload the record data
108833         me.loadRecord(record);
108834
108835         if (!me.isVisible()) {
108836             me.show();
108837             me.focusContextCell();
108838         } else {
108839             me.reposition({
108840                 callback: this.focusContextCell
108841             });
108842         }
108843     },
108844
108845     // Focus the cell on start edit based upon the current context
108846     focusContextCell: function() {
108847         var field = this.getEditor(this.context.colIdx);
108848         if (field && field.focus) {
108849             field.focus();
108850         }
108851     },
108852
108853     cancelEdit: function() {
108854         var me = this,
108855             form = me.getForm();
108856
108857         me.hide();
108858         form.clearInvalid();
108859         form.reset();
108860     },
108861
108862     completeEdit: function() {
108863         var me = this,
108864             form = me.getForm();
108865
108866         if (!form.isValid()) {
108867             return;
108868         }
108869
108870         form.updateRecord(me.context.record);
108871         me.hide();
108872         return true;
108873     },
108874
108875     onShow: function() {
108876         var me = this;
108877         me.callParent(arguments);
108878         me.reposition();
108879     },
108880
108881     onHide: function() {
108882         var me = this;
108883         me.callParent(arguments);
108884         me.hideToolTip();
108885         me.invalidateScroller();
108886         if (me.context) {
108887             me.context.view.focus();
108888             me.context = null;
108889         }
108890     },
108891
108892     isDirty: function() {
108893         var me = this,
108894             form = me.getForm();
108895         return form.isDirty();
108896     },
108897
108898     getToolTip: function() {
108899         var me = this,
108900             tip;
108901
108902         if (!me.tooltip) {
108903             tip = me.tooltip = Ext.createWidget('tooltip', {
108904                 cls: Ext.baseCSSPrefix + 'grid-row-editor-errors',
108905                 title: me.errorsText,
108906                 autoHide: false,
108907                 closable: true,
108908                 closeAction: 'disable',
108909                 anchor: 'left'
108910             });
108911         }
108912         return me.tooltip;
108913     },
108914
108915     hideToolTip: function() {
108916         var me = this,
108917             tip = me.getToolTip();
108918         if (tip.rendered) {
108919             tip.disable();
108920         }
108921         me.hiddenTip = false;
108922     },
108923
108924     showToolTip: function() {
108925         var me = this,
108926             tip = me.getToolTip(),
108927             context = me.context,
108928             row = Ext.get(context.row),
108929             viewEl = context.grid.view.el;
108930
108931         tip.setTarget(row);
108932         tip.showAt([-10000, -10000]);
108933         tip.body.update(me.getErrors());
108934         tip.mouseOffset = [viewEl.getWidth() - row.getWidth() + me.lastScrollLeft + 15, 0];
108935         me.repositionTip();
108936         tip.doLayout();
108937         tip.enable();
108938     },
108939
108940     repositionTip: function() {
108941         var me = this,
108942             tip = me.getToolTip(),
108943             context = me.context,
108944             row = Ext.get(context.row),
108945             viewEl = context.grid.view.el,
108946             viewHeight = viewEl.getHeight(),
108947             viewTop = me.lastScrollTop,
108948             viewBottom = viewTop + viewHeight,
108949             rowHeight = row.getHeight(),
108950             rowTop = row.dom.offsetTop,
108951             rowBottom = rowTop + rowHeight;
108952
108953         if (rowBottom > viewTop && rowTop < viewBottom) {
108954             tip.show();
108955             me.hiddenTip = false;
108956         } else {
108957             tip.hide();
108958             me.hiddenTip = true;
108959         }
108960     },
108961
108962     getErrors: function() {
108963         var me = this,
108964             dirtyText = !me.autoCancel && me.isDirty() ? me.dirtyText + '<br />' : '',
108965             errors = [];
108966
108967         Ext.Array.forEach(me.query('>[isFormField]'), function(field) {
108968             errors = errors.concat(
108969                 Ext.Array.map(field.getErrors(), function(e) {
108970                     return '<li>' + e + '</li>';
108971                 })
108972             );
108973         }, me);
108974
108975         return dirtyText + '<ul>' + errors.join('') + '</ul>';
108976     },
108977
108978     invalidateScroller: function() {
108979         var me = this,
108980             context = me.context,
108981             scroller = context.grid.verticalScroller;
108982
108983         if (scroller) {
108984             scroller.invalidate();
108985         }
108986     }
108987 });
108988 /**
108989  * @class Ext.grid.header.Container
108990  * @extends Ext.container.Container
108991  * @private
108992  *
108993  * Container which holds headers and is docked at the top or bottom of a TablePanel.
108994  * The HeaderContainer drives resizing/moving/hiding of columns within the TableView.
108995  * As headers are hidden, moved or resized the headercontainer is responsible for
108996  * triggering changes within the view.
108997  *
108998  * @xtype headercontainer
108999  */
109000 Ext.define('Ext.grid.header.Container', {
109001     extend: 'Ext.container.Container',
109002     uses: [
109003         'Ext.grid.ColumnLayout',
109004         'Ext.grid.column.Column',
109005         'Ext.menu.Menu',
109006         'Ext.menu.CheckItem',
109007         'Ext.menu.Separator',
109008         'Ext.grid.plugin.HeaderResizer',
109009         'Ext.grid.plugin.HeaderReorderer'
109010     ],
109011     border: true,
109012
109013     alias: 'widget.headercontainer',
109014
109015     baseCls: Ext.baseCSSPrefix + 'grid-header-ct',
109016     dock: 'top',
109017
109018     /**
109019      * @cfg {Number} weight
109020      * HeaderContainer overrides the default weight of 0 for all docked items to 100.
109021      * This is so that it has more priority over things like toolbars.
109022      */
109023     weight: 100,
109024     defaultType: 'gridcolumn',
109025     /**
109026      * @cfg {Number} defaultWidth
109027      * Width of the header if no width or flex is specified. Defaults to 100.
109028      */
109029     defaultWidth: 100,
109030
109031
109032     sortAscText: 'Sort Ascending',
109033     sortDescText: 'Sort Descending',
109034     sortClearText: 'Clear Sort',
109035     columnsText: 'Columns',
109036
109037     lastHeaderCls: Ext.baseCSSPrefix + 'column-header-last',
109038     firstHeaderCls: Ext.baseCSSPrefix + 'column-header-first',
109039     headerOpenCls: Ext.baseCSSPrefix + 'column-header-open',
109040
109041     // private; will probably be removed by 4.0
109042     triStateSort: false,
109043
109044     ddLock: false,
109045
109046     dragging: false,
109047
109048     /**
109049      * <code>true</code> if this HeaderContainer is in fact a group header which contains sub headers.
109050      * @type Boolean
109051      * @property isGroupHeader
109052      */
109053
109054     /**
109055      * @cfg {Boolean} sortable
109056      * Provides the default sortable state for all Headers within this HeaderContainer.
109057      * Also turns on or off the menus in the HeaderContainer. Note that the menu is
109058      * shared across every header and therefore turning it off will remove the menu
109059      * items for every header.
109060      */
109061     sortable: true,
109062     
109063     initComponent: function() {
109064         var me = this;
109065         
109066         me.headerCounter = 0;
109067         me.plugins = me.plugins || [];
109068
109069         // TODO: Pass in configurations to turn on/off dynamic
109070         //       resizing and disable resizing all together
109071
109072         // Only set up a Resizer and Reorderer for the topmost HeaderContainer.
109073         // Nested Group Headers are themselves HeaderContainers
109074         if (!me.isHeader) {
109075             me.resizer   = Ext.create('Ext.grid.plugin.HeaderResizer');
109076             me.reorderer = Ext.create('Ext.grid.plugin.HeaderReorderer');
109077             if (!me.enableColumnResize) {
109078                 me.resizer.disable();
109079             } 
109080             if (!me.enableColumnMove) {
109081                 me.reorderer.disable();
109082             }
109083             me.plugins.push(me.reorderer, me.resizer);
109084         }
109085
109086         // Base headers do not need a box layout
109087         if (me.isHeader && !me.items) {
109088             me.layout = 'auto';
109089         }
109090         // HeaderContainer and Group header needs a gridcolumn layout.
109091         else {
109092             me.layout = {
109093                 type: 'gridcolumn',
109094                 availableSpaceOffset: me.availableSpaceOffset,
109095                 align: 'stretchmax',
109096                 resetStretch: true
109097             };
109098         }
109099         me.defaults = me.defaults || {};
109100         Ext.applyIf(me.defaults, {
109101             width: me.defaultWidth,
109102             triStateSort: me.triStateSort,
109103             sortable: me.sortable
109104         });
109105         me.callParent();
109106         me.addEvents(
109107             /**
109108              * @event columnresize
109109              * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
109110              * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
109111              * @param {Number} width
109112              */
109113             'columnresize',
109114
109115             /**
109116              * @event headerclick
109117              * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
109118              * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
109119              * @param {Ext.EventObject} e
109120              * @param {HTMLElement} t
109121              */
109122             'headerclick',
109123
109124             /**
109125              * @event headertriggerclick
109126              * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
109127              * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
109128              * @param {Ext.EventObject} e
109129              * @param {HTMLElement} t
109130              */
109131             'headertriggerclick',
109132
109133             /**
109134              * @event columnmove
109135              * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
109136              * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
109137              * @param {Number} fromIdx
109138              * @param {Number} toIdx
109139              */
109140             'columnmove',
109141             /**
109142              * @event columnhide
109143              * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
109144              * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
109145              */
109146             'columnhide',
109147             /**
109148              * @event columnshow
109149              * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
109150              * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
109151              */
109152             'columnshow',
109153             /**
109154              * @event sortchange
109155              * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
109156              * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
109157              * @param {String} direction
109158              */
109159             'sortchange',
109160             /**
109161              * @event menucreate
109162              * Fired immediately after the column header menu is created.
109163              * @param {Ext.grid.header.Container} ct This instance
109164              * @param {Ext.menu.Menu} menu The Menu that was created
109165              */
109166             'menucreate'
109167         );
109168     },
109169
109170     onDestroy: function() {
109171         Ext.destroy(this.resizer, this.reorderer);
109172         this.callParent();
109173     },
109174
109175     // Invalidate column cache on add
109176     // We cannot refresh the View on every add because this method is called
109177     // when the HeaderDropZone moves Headers around, that will also refresh the view
109178     onAdd: function(c) {
109179         var me = this;
109180         if (!c.headerId) {
109181             c.headerId = 'h' + (++me.headerCounter);
109182         }
109183         me.callParent(arguments);
109184         me.purgeCache();
109185     },
109186
109187     // Invalidate column cache on remove
109188     // We cannot refresh the View on every remove because this method is called
109189     // when the HeaderDropZone moves Headers around, that will also refresh the view
109190     onRemove: function(c) {
109191         var me = this;
109192         me.callParent(arguments);
109193         me.purgeCache();
109194     },
109195
109196     afterRender: function() {
109197         this.callParent();
109198         var store   = this.up('[store]').store,
109199             sorters = store.sorters,
109200             first   = sorters.first(),
109201             hd;
109202
109203         if (first) {
109204             hd = this.down('gridcolumn[dataIndex=' + first.property  +']');
109205             if (hd) {
109206                 hd.setSortState(first.direction, false, true);
109207             }
109208         }
109209     },
109210
109211     afterLayout: function() {
109212         if (!this.isHeader) {
109213             var me = this,
109214                 topHeaders = me.query('>gridcolumn:not([hidden])'),
109215                 viewEl;
109216
109217             me.callParent(arguments);
109218
109219             if (topHeaders.length) {
109220                 topHeaders[0].el.radioCls(me.firstHeaderCls);
109221                 topHeaders[topHeaders.length - 1].el.radioCls(me.lastHeaderCls);
109222             }
109223         }
109224     },
109225
109226     onHeaderShow: function(header) {
109227         // Pass up to the GridSection
109228         var me = this,
109229             gridSection = me.ownerCt,
109230             menu = me.getMenu(),
109231             topItems, topItemsVisible,
109232             colCheckItem,
109233             itemToEnable,
109234             len, i;
109235
109236         if (menu) {
109237
109238             colCheckItem = menu.down('menucheckitem[headerId=' + header.id + ']');
109239             if (colCheckItem) {
109240                 colCheckItem.setChecked(true, true);
109241             }
109242
109243             // There's more than one header visible, and we've disabled some checked items... re-enable them
109244             topItems = menu.query('#columnItem>menucheckitem[checked]');
109245             topItemsVisible = topItems.length;
109246             if ((me.getVisibleGridColumns().length > 1) && me.disabledMenuItems && me.disabledMenuItems.length) {
109247                 if (topItemsVisible == 1) {
109248                     Ext.Array.remove(me.disabledMenuItems, topItems[0]);
109249                 }
109250                 for (i = 0, len = me.disabledMenuItems.length; i < len; i++) {
109251                     itemToEnable = me.disabledMenuItems[i];
109252                     if (!itemToEnable.isDestroyed) {
109253                         itemToEnable[itemToEnable.menu ? 'enableCheckChange' : 'enable']();
109254                     }
109255                 }
109256                 if (topItemsVisible == 1) {
109257                     me.disabledMenuItems = topItems;
109258                 } else {
109259                     me.disabledMenuItems = [];
109260                 }
109261             }
109262         }
109263
109264         // Only update the grid UI when we are notified about base level Header shows;
109265         // Group header shows just cause a layout of the HeaderContainer
109266         if (!header.isGroupHeader) {
109267             if (me.view) {
109268                 me.view.onHeaderShow(me, header, true);
109269             }
109270             if (gridSection) {
109271                 gridSection.onHeaderShow(me, header);
109272             }
109273         }
109274         me.fireEvent('columnshow', me, header);
109275
109276         // The header's own hide suppresses cascading layouts, so lay the headers out now
109277         me.doLayout();
109278     },
109279
109280     onHeaderHide: function(header, suppressLayout) {
109281         // Pass up to the GridSection
109282         var me = this,
109283             gridSection = me.ownerCt,
109284             menu = me.getMenu(),
109285             colCheckItem;
109286
109287         if (menu) {
109288
109289             // If the header was hidden programmatically, sync the Menu state
109290             colCheckItem = menu.down('menucheckitem[headerId=' + header.id + ']');
109291             if (colCheckItem) {
109292                 colCheckItem.setChecked(false, true);
109293             }
109294             me.setDisabledItems();
109295         }
109296
109297         // Only update the UI when we are notified about base level Header hides;
109298         if (!header.isGroupHeader) {
109299             if (me.view) {
109300                 me.view.onHeaderHide(me, header, true);
109301             }
109302             if (gridSection) {
109303                 gridSection.onHeaderHide(me, header);
109304             }
109305
109306             // The header's own hide suppresses cascading layouts, so lay the headers out now
109307             if (!suppressLayout) {
109308                 me.doLayout();
109309             }
109310         }
109311         me.fireEvent('columnhide', me, header);
109312     },
109313
109314     setDisabledItems: function(){
109315         var me = this,
109316             menu = me.getMenu(),
109317             i = 0,
109318             len,
109319             itemsToDisable,
109320             itemToDisable;
109321
109322         // Find what to disable. If only one top level item remaining checked, we have to disable stuff.
109323         itemsToDisable = menu.query('#columnItem>menucheckitem[checked]');
109324         if ((itemsToDisable.length === 1)) {
109325             if (!me.disabledMenuItems) {
109326                 me.disabledMenuItems = [];
109327             }
109328
109329             // If down to only one column visible, also disable any descendant checkitems
109330             if ((me.getVisibleGridColumns().length === 1) && itemsToDisable[0].menu) {
109331                 itemsToDisable = itemsToDisable.concat(itemsToDisable[0].menu.query('menucheckitem[checked]'));
109332             }
109333
109334             len = itemsToDisable.length;
109335             // Disable any further unchecking at any level.
109336             for (i = 0; i < len; i++) {
109337                 itemToDisable = itemsToDisable[i];
109338                 if (!Ext.Array.contains(me.disabledMenuItems, itemToDisable)) {
109339                     itemToDisable[itemToDisable.menu ? 'disableCheckChange' : 'disable']();
109340                     me.disabledMenuItems.push(itemToDisable);
109341                 }
109342             }
109343         }
109344     },
109345
109346     /**
109347      * Temporarily lock the headerCt. This makes it so that clicking on headers
109348      * don't trigger actions like sorting or opening of the header menu. This is
109349      * done because extraneous events may be fired on the headers after interacting
109350      * with a drag drop operation.
109351      * @private
109352      */
109353     tempLock: function() {
109354         this.ddLock = true;
109355         Ext.Function.defer(function() {
109356             this.ddLock = false;
109357         }, 200, this);
109358     },
109359
109360     onHeaderResize: function(header, w, suppressFocus) {
109361         this.tempLock();
109362         if (this.view && this.view.rendered) {
109363             this.view.onHeaderResize(header, w, suppressFocus);
109364         }
109365         this.fireEvent('columnresize', this, header, w);
109366     },
109367
109368     onHeaderClick: function(header, e, t) {
109369         this.fireEvent("headerclick", this, header, e, t);
109370     },
109371
109372     onHeaderTriggerClick: function(header, e, t) {
109373         // generate and cache menu, provide ability to cancel/etc
109374         if (this.fireEvent("headertriggerclick", this, header, e, t) !== false) {
109375             this.showMenuBy(t, header);
109376         }
109377     },
109378
109379     showMenuBy: function(t, header) {
109380         var menu = this.getMenu(),
109381             ascItem  = menu.down('#ascItem'),
109382             descItem = menu.down('#descItem'),
109383             sortableMth;
109384
109385         menu.activeHeader = menu.ownerCt = header;
109386         menu.setFloatParent(header);
109387         // TODO: remove coupling to Header's titleContainer el
109388         header.titleContainer.addCls(this.headerOpenCls);
109389
109390         // enable or disable asc & desc menu items based on header being sortable
109391         sortableMth = header.sortable ? 'enable' : 'disable';
109392         if (ascItem) {
109393             ascItem[sortableMth]();
109394         }
109395         if (descItem) {
109396             descItem[sortableMth]();
109397         }
109398         menu.showBy(t);
109399     },
109400
109401     // remove the trigger open class when the menu is hidden
109402     onMenuDeactivate: function() {
109403         var menu = this.getMenu();
109404         // TODO: remove coupling to Header's titleContainer el
109405         menu.activeHeader.titleContainer.removeCls(this.headerOpenCls);
109406     },
109407
109408     moveHeader: function(fromIdx, toIdx) {
109409
109410         // An automatically expiring lock
109411         this.tempLock();
109412         this.onHeaderMoved(this.move(fromIdx, toIdx), fromIdx, toIdx);
109413     },
109414
109415     purgeCache: function() {
109416         var me = this;
109417         // Delete column cache - column order has changed.
109418         delete me.gridDataColumns;
109419
109420         // Menu changes when columns are moved. It will be recreated.
109421         if (me.menu) {
109422             me.menu.destroy();
109423             delete me.menu;
109424         }
109425     },
109426
109427     onHeaderMoved: function(header, fromIdx, toIdx) {
109428         var me = this,
109429             gridSection = me.ownerCt;
109430
109431         if (gridSection) {
109432             gridSection.onHeaderMove(me, header, fromIdx, toIdx);
109433         }
109434         me.fireEvent("columnmove", me, header, fromIdx, toIdx);
109435     },
109436
109437     /**
109438      * Gets the menu (and will create it if it doesn't already exist)
109439      * @private
109440      */
109441     getMenu: function() {
109442         var me = this;
109443
109444         if (!me.menu) {
109445             me.menu = Ext.create('Ext.menu.Menu', {
109446                 items: me.getMenuItems(),
109447                 listeners: {
109448                     deactivate: me.onMenuDeactivate,
109449                     scope: me
109450                 }
109451             });
109452             me.setDisabledItems();
109453             me.fireEvent('menucreate', me, me.menu);
109454         }
109455         return me.menu;
109456     },
109457
109458     /**
109459      * Returns an array of menu items to be placed into the shared menu
109460      * across all headers in this header container.
109461      * @returns {Array} menuItems
109462      */
109463     getMenuItems: function() {
109464         var me = this,
109465             menuItems = [{
109466                 itemId: 'columnItem',
109467                 text: me.columnsText,
109468                 cls: Ext.baseCSSPrefix + 'cols-icon',
109469                 menu: me.getColumnMenu(me)
109470             }];
109471
109472         if (me.sortable) {
109473             menuItems.unshift({
109474                 itemId: 'ascItem',
109475                 text: me.sortAscText,
109476                 cls: 'xg-hmenu-sort-asc',
109477                 handler: me.onSortAscClick,
109478                 scope: me
109479             },{
109480                 itemId: 'descItem',
109481                 text: me.sortDescText,
109482                 cls: 'xg-hmenu-sort-desc',
109483                 handler: me.onSortDescClick,
109484                 scope: me
109485             },'-');
109486         }
109487         return menuItems;
109488     },
109489
109490     // sort asc when clicking on item in menu
109491     onSortAscClick: function() {
109492         var menu = this.getMenu(),
109493             activeHeader = menu.activeHeader;
109494
109495         activeHeader.setSortState('ASC');
109496     },
109497
109498     // sort desc when clicking on item in menu
109499     onSortDescClick: function() {
109500         var menu = this.getMenu(),
109501             activeHeader = menu.activeHeader;
109502
109503         activeHeader.setSortState('DESC');
109504     },
109505
109506     /**
109507      * Returns an array of menu CheckItems corresponding to all immediate children of the passed Container which have been configured as hideable.
109508      */
109509     getColumnMenu: function(headerContainer) {
109510         var menuItems = [],
109511             i = 0,
109512             item,
109513             items = headerContainer.query('>gridcolumn[hideable]'),
109514             itemsLn = items.length,
109515             menuItem;
109516
109517         for (; i < itemsLn; i++) {
109518             item = items[i];
109519             menuItem = Ext.create('Ext.menu.CheckItem', {
109520                 text: item.text,
109521                 checked: !item.hidden,
109522                 hideOnClick: false,
109523                 headerId: item.id,
109524                 menu: item.isGroupHeader ? this.getColumnMenu(item) : undefined,
109525                 checkHandler: this.onColumnCheckChange,
109526                 scope: this
109527             });
109528             if (itemsLn === 1) {
109529                 menuItem.disabled = true;
109530             }
109531             menuItems.push(menuItem);
109532
109533             // If the header is ever destroyed - for instance by dragging out the last remaining sub header,
109534             // then the associated menu item must also be destroyed.
109535             item.on({
109536                 destroy: Ext.Function.bind(menuItem.destroy, menuItem)
109537             });
109538         }
109539         return menuItems;
109540     },
109541
109542     onColumnCheckChange: function(checkItem, checked) {
109543         var header = Ext.getCmp(checkItem.headerId);
109544         header[checked ? 'show' : 'hide']();
109545     },
109546
109547     /**
109548      * Get the columns used for generating a template via TableChunker.
109549      * Returns an array of all columns and their
109550      *  - dataIndex
109551      *  - align
109552      *  - width
109553      *  - id
109554      *  - columnId - used to create an identifying CSS class
109555      *  - cls The tdCls configuration from the Column object
109556      *  @private
109557      */
109558     getColumnsForTpl: function(flushCache) {
109559         var cols    = [],
109560             headers   = this.getGridColumns(flushCache),
109561             headersLn = headers.length,
109562             i = 0,
109563             header;
109564
109565         for (; i < headersLn; i++) {
109566             header = headers[i];
109567             cols.push({
109568                 dataIndex: header.dataIndex,
109569                 align: header.align,
109570                 width: header.hidden ? 0 : header.getDesiredWidth(),
109571                 id: header.id,
109572                 cls: header.tdCls,
109573                 columnId: header.getItemId()
109574             });
109575         }
109576         return cols;
109577     },
109578
109579     /**
109580      * Returns the number of <b>grid columns</b> descended from this HeaderContainer.
109581      * Group Columns are HeaderContainers. All grid columns are returned, including hidden ones.
109582      */
109583     getColumnCount: function() {
109584         return this.getGridColumns().length;
109585     },
109586
109587     /**
109588      * Gets the full width of all columns that are visible.
109589      */
109590     getFullWidth: function(flushCache) {
109591         var fullWidth = 0,
109592             headers     = this.getVisibleGridColumns(flushCache),
109593             headersLn   = headers.length,
109594             i         = 0;
109595
109596         for (; i < headersLn; i++) {
109597             if (!isNaN(headers[i].width)) {
109598                 // use headers getDesiredWidth if its there
109599                 if (headers[i].getDesiredWidth) {
109600                     fullWidth += headers[i].getDesiredWidth();
109601                 // if injected a diff cmp use getWidth
109602                 } else {
109603                     fullWidth += headers[i].getWidth();
109604                 }
109605             }
109606         }
109607         return fullWidth;
109608     },
109609
109610     // invoked internally by a header when not using triStateSorting
109611     clearOtherSortStates: function(activeHeader) {
109612         var headers   = this.getGridColumns(),
109613             headersLn = headers.length,
109614             i         = 0,
109615             oldSortState;
109616
109617         for (; i < headersLn; i++) {
109618             if (headers[i] !== activeHeader) {
109619                 oldSortState = headers[i].sortState;
109620                 // unset the sortstate and dont recurse
109621                 headers[i].setSortState(null, true);
109622                 //if (!silent && oldSortState !== null) {
109623                 //    this.fireEvent('sortchange', this, headers[i], null);
109624                 //}
109625             }
109626         }
109627     },
109628
109629     /**
109630      * Returns an array of the <b>visible<b> columns in the grid. This goes down to the lowest column header
109631      * level, and does not return <i>grouped</i> headers which contain sub headers.
109632      * @param {Boolean} refreshCache If omitted, the cached set of columns will be returned. Pass true to refresh the cache.
109633      * @returns {Array}
109634      */
109635     getVisibleGridColumns: function(refreshCache) {
109636         return Ext.ComponentQuery.query(':not([hidden])', this.getGridColumns(refreshCache));
109637     },
109638
109639     /**
109640      * Returns an array of all columns which map to Store fields. This goes down to the lowest column header
109641      * level, and does not return <i>grouped</i> headers which contain sub headers.
109642      * @param {Boolean} refreshCache If omitted, the cached set of columns will be returned. Pass true to refresh the cache.
109643      * @returns {Array}
109644      */
109645     getGridColumns: function(refreshCache) {
109646         var me = this,
109647             result = refreshCache ? null : me.gridDataColumns;
109648
109649         // Not already got the column cache, so collect the base columns
109650         if (!result) {
109651             me.gridDataColumns = result = [];
109652             me.cascade(function(c) {
109653                 if ((c !== me) && !c.isGroupHeader) {
109654                     result.push(c);
109655                 }
109656             });
109657         }
109658
109659         return result;
109660     },
109661
109662     /**
109663      * Get the index of a leaf level header regardless of what the nesting
109664      * structure is.
109665      */
109666     getHeaderIndex: function(header) {
109667         var columns = this.getGridColumns();
109668         return Ext.Array.indexOf(columns, header);
109669     },
109670
109671     /**
109672      * Get a leaf level header by index regardless of what the nesting
109673      * structure is.
109674      */
109675     getHeaderAtIndex: function(index) {
109676         var columns = this.getGridColumns();
109677         return columns[index];
109678     },
109679
109680     /**
109681      * Maps the record data to base it on the header id's.
109682      * This correlates to the markup/template generated by
109683      * TableChunker.
109684      */
109685     prepareData: function(data, rowIdx, record, view) {
109686         var obj       = {},
109687             headers   = this.getGridColumns(),
109688             headersLn = headers.length,
109689             colIdx    = 0,
109690             header, value,
109691             metaData,
109692             g = this.up('tablepanel'),
109693             store = g.store;
109694
109695         for (; colIdx < headersLn; colIdx++) {
109696             metaData = {
109697                 tdCls: '',
109698                 style: ''
109699             };
109700             header = headers[colIdx];
109701             value = data[header.dataIndex];
109702
109703             // When specifying a renderer as a string, it always resolves
109704             // to Ext.util.Format
109705             if (Ext.isString(header.renderer)) {
109706                 header.renderer = Ext.util.Format[header.renderer];
109707             }
109708
109709             if (Ext.isFunction(header.renderer)) {
109710                 value = header.renderer.call(
109711                     header.scope || this.ownerCt,
109712                     value,
109713                     // metadata per cell passing an obj by reference so that
109714                     // it can be manipulated inside the renderer
109715                     metaData,
109716                     record,
109717                     rowIdx,
109718                     colIdx,
109719                     store,
109720                     view
109721                 );
109722             }
109723
109724             if (metaData.css) {
109725                 // This warning attribute is used by the compat layer
109726                 obj.cssWarning = true;
109727                 metaData.tdCls = metaData.css;
109728                 delete metaData.css;
109729             }
109730             obj[header.id+'-modified'] = record.isModified(header.dataIndex) ? Ext.baseCSSPrefix + 'grid-dirty-cell' : Ext.baseCSSPrefix + 'grid-clean-cell';
109731             obj[header.id+'-tdCls'] = metaData.tdCls;
109732             obj[header.id+'-tdAttr'] = metaData.tdAttr;
109733             obj[header.id+'-style'] = metaData.style;
109734             if (value === undefined || value === null || value === '') {
109735                 value = '&#160;';
109736             }
109737             obj[header.id] = value;
109738         }
109739         return obj;
109740     },
109741
109742     expandToFit: function(header) {
109743         if (this.view) {
109744             this.view.expandToFit(header);
109745         }
109746     }
109747 });
109748
109749 /**
109750  * @class Ext.grid.column.Column
109751  * @extends Ext.grid.header.Container
109752  * 
109753  * This class specifies the definition for a column inside a {@link Ext.grid.Panel}. It encompasses
109754  * both the grid header configuration as well as displaying data within the grid itself. If the
109755  * {@link #columns} configuration is specified, this column will become a column group and can
109756  * container other columns inside. In general, this class will not be created directly, rather
109757  * an array of column configurations will be passed to the grid:
109758  * 
109759  * {@img Ext.grid.column.Column/Ext.grid.column.Column.png Ext.grid.column.Column grid column}
109760  *
109761  * ## Code
109762  *    Ext.create('Ext.data.Store', {
109763  *        storeId:'employeeStore',
109764  *        fields:['firstname', 'lastname', 'senority', 'dep', 'hired'],
109765  *        data:[
109766  *            {firstname:"Michael", lastname:"Scott", senority:7, dep:"Manangement", hired:"01/10/2004"},
109767  *            {firstname:"Dwight", lastname:"Schrute", senority:2, dep:"Sales", hired:"04/01/2004"},
109768  *            {firstname:"Jim", lastname:"Halpert", senority:3, dep:"Sales", hired:"02/22/2006"},
109769  *            {firstname:"Kevin", lastname:"Malone", senority:4, dep:"Accounting", hired:"06/10/2007"},
109770  *            {firstname:"Angela", lastname:"Martin", senority:5, dep:"Accounting", hired:"10/21/2008"}                        
109771  *        ]
109772  *    });
109773  *    
109774  *    Ext.create('Ext.grid.Panel', {
109775  *        title: 'Column Demo',
109776  *        store: Ext.data.StoreManager.lookup('employeeStore'),
109777  *        columns: [
109778  *            {text: 'First Name',  dataIndex:'firstname'},
109779  *            {text: 'Last Name',  dataIndex:'lastname'},
109780  *            {text: 'Hired Month',  dataIndex:'hired', xtype:'datecolumn', format:'M'},              
109781  *            {text: 'Deparment (Yrs)', xtype:'templatecolumn', tpl:'{dep} ({senority})'}
109782  *        ],
109783  *        width: 400,
109784  *        renderTo: Ext.getBody()
109785  *    });
109786  *     
109787  * ## Convenience Subclasses
109788  * There are several column subclasses that provide default rendering for various data types
109789  *
109790  *  - {@link Ext.grid.column.Action}: Renders icons that can respond to click events inline
109791  *  - {@link Ext.grid.column.Boolean}: Renders for boolean values 
109792  *  - {@link Ext.grid.column.Date}: Renders for date values
109793  *  - {@link Ext.grid.column.Number}: Renders for numeric values
109794  *  - {@link Ext.grid.column.Template}: Renders a value using an {@link Ext.XTemplate} using the record data 
109795  * 
109796  * ## Setting Sizes
109797  * The columns are laid out by a {@link Ext.layout.container.HBox} layout, so a column can either
109798  * be given an explicit width value or a flex configuration. If no width is specified the grid will
109799  * automatically the size the column to 100px. For column groups, the size is calculated by measuring
109800  * the width of the child columns, so a width option should not be specified in that case.
109801  * 
109802  * ## Header Options
109803  *  - {@link #text}: Sets the header text for the column
109804  *  - {@link #sortable}: Specifies whether the column can be sorted by clicking the header or using the column menu
109805  *  - {@link #hideable}: Specifies whether the column can be hidden using the column menu
109806  *  - {@link #menuDisabled}: Disables the column header menu
109807  *  - {@link #draggable}: Specifies whether the column header can be reordered by dragging
109808  *  - {@link #groupable}: Specifies whether the grid can be grouped by the column dataIndex. See also {@link Ext.grid.feature.Grouping}
109809  * 
109810  * ## Data Options
109811  *  - {@link #dataIndex}: The dataIndex is the field in the underlying {@link Ext.data.Store} to use as the value for the column.
109812  *  - {@link #renderer}: Allows the underlying store value to be transformed before being displayed in the grid
109813  * 
109814  * @xtype gridcolumn
109815  */
109816 Ext.define('Ext.grid.column.Column', {
109817     extend: 'Ext.grid.header.Container',
109818     alias: 'widget.gridcolumn',
109819     requires: ['Ext.util.KeyNav'],
109820     alternateClassName: 'Ext.grid.Column',
109821
109822     baseCls: Ext.baseCSSPrefix + 'column-header ' + Ext.baseCSSPrefix + 'unselectable',
109823
109824     // Not the standard, automatically applied overCls because we must filter out overs of child headers.
109825     hoverCls: Ext.baseCSSPrefix + 'column-header-over',
109826
109827     handleWidth: 5,
109828
109829     sortState: null,
109830
109831     possibleSortStates: ['ASC', 'DESC'],
109832
109833     renderTpl:
109834         '<div class="' + Ext.baseCSSPrefix + 'column-header-inner">' +
109835             '<span class="' + Ext.baseCSSPrefix + 'column-header-text">' +
109836                 '{text}' +
109837             '</span>' +
109838             '<tpl if="!values.menuDisabled"><div class="' + Ext.baseCSSPrefix + 'column-header-trigger"></div></tpl>' +
109839         '</div>',
109840
109841     /**
109842      * @cfg {Array} columns
109843      * <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>
109844      * <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
109845      * if all sub columns are dragged out of a group, the group is destroyed.
109846      */
109847
109848     /**
109849      * @cfg {String} dataIndex <p><b>Required</b>. The name of the field in the
109850      * grid's {@link Ext.data.Store}'s {@link Ext.data.Model} definition from
109851      * which to draw the column's value.</p>
109852      */
109853     dataIndex: null,
109854
109855     /**
109856      * @cfg {String} text Optional. The header text to be used as innerHTML
109857      * (html tags are accepted) to display in the Grid.  <b>Note</b>: to
109858      * have a clickable header with no text displayed you can use the
109859      * default of <tt>'&#160;'</tt>.
109860      */
109861     text: '&#160',
109862
109863     /**
109864      * @cfg {Boolean} sortable Optional. <tt>true</tt> if sorting is to be allowed on this column.
109865      * Whether local/remote sorting is used is specified in <code>{@link Ext.data.Store#remoteSort}</code>.
109866      */
109867     sortable: true,
109868     
109869     /**
109870      * @cfg {Boolean} groupable Optional. If the grid uses a {@link Ext.grid.feature.Grouping}, this option
109871      * may be used to disable the header menu item to group by the column selected. By default,
109872      * the header menu group option is enabled. Set to false to disable (but still show) the
109873      * group option in the header menu for the column.
109874      */
109875      
109876     /**
109877      * @cfg {Boolean} hideable Optional. Specify as <tt>false</tt> to prevent the user from hiding this column
109878      * (defaults to true).
109879      */
109880     hideable: true,
109881
109882     /**
109883      * @cfg {Boolean} menuDisabled
109884      * True to disabled the column header menu containing sort/hide options. Defaults to false.
109885      */
109886     menuDisabled: false,
109887
109888     /**
109889      * @cfg {Function} renderer
109890      * <p>A renderer is an 'interceptor' method which can be used transform data (value, appearance, etc.) before it
109891      * is rendered. Example:</p>
109892      * <pre><code>{
109893     renderer: function(value){
109894         if (value === 1) {
109895             return '1 person';
109896         }
109897         return value + ' people';
109898     }
109899 }
109900      * </code></pre>
109901      * @param {Mixed} value The data value for the current cell
109902      * @param {Object} metaData A collection of metadata about the current cell; can be used or modified by
109903      * the renderer. Recognized properties are: <tt>tdCls</tt>, <tt>tdAttr</tt>, and <tt>style</tt>.
109904      * @param {Ext.data.Model} record The record for the current row
109905      * @param {Number} rowIndex The index of the current row
109906      * @param {Number} colIndex The index of the current column
109907      * @param {Ext.data.Store} store The data store
109908      * @param {Ext.view.View} view The current view
109909      * @return {String} The HTML to be rendered
109910      */
109911     renderer: false,
109912
109913     /**
109914      * @cfg {String} align Sets the alignment of the header and rendered columns.
109915      * Defaults to 'left'.
109916      */
109917     align: 'left',
109918
109919     /**
109920      * @cfg {Boolean} draggable Indicates whether or not the header can be drag and drop re-ordered.
109921      * Defaults to true.
109922      */
109923     draggable: true,
109924
109925     // Header does not use the typical ComponentDraggable class and therefore we
109926     // override this with an emptyFn. It is controlled at the HeaderDragZone.
109927     initDraggable: Ext.emptyFn,
109928
109929     /**
109930      * @cfg {String} tdCls <p>Optional. A CSS class names to apply to the table cells for this column.</p>
109931      */
109932
109933     /**
109934      * @property {Ext.core.Element} triggerEl
109935      */
109936
109937     /**
109938      * @property {Ext.core.Element} textEl
109939      */
109940
109941     /**
109942      * @private
109943      * Set in this class to identify, at runtime, instances which are not instances of the
109944      * HeaderContainer base class, but are in fact, the subclass: Header.
109945      */
109946     isHeader: true,
109947
109948     initComponent: function() {
109949         var me = this,
109950             i,
109951             len;
109952         
109953         if (Ext.isDefined(me.header)) {
109954             me.text = me.header;
109955             delete me.header;
109956         }
109957
109958         // Flexed Headers need to have a minWidth defined so that they can never be squeezed out of existence by the
109959         // HeaderContainer's specialized Box layout, the ColumnLayout. The ColumnLayout's overridden calculateChildboxes
109960         // method extends the available layout space to accommodate the "desiredWidth" of all the columns.
109961         if (me.flex) {
109962             me.minWidth = me.minWidth || Ext.grid.plugin.HeaderResizer.prototype.minColWidth;
109963         }
109964         // Non-flexed Headers may never be squeezed in the event of a shortfall so
109965         // always set their minWidth to their current width.
109966         else {
109967             me.minWidth = me.width;
109968         }
109969
109970         if (!me.triStateSort) {
109971             me.possibleSortStates.length = 2;
109972         }
109973
109974         // A group header; It contains items which are themselves Headers
109975         if (Ext.isDefined(me.columns)) {
109976             me.isGroupHeader = true;
109977
109978             if (me.dataIndex) {
109979                 Ext.Error.raise('Ext.grid.column.Column: Group header may not accept a dataIndex');
109980             }
109981             if ((me.width && me.width !== Ext.grid.header.Container.prototype.defaultWidth) || me.flex) {
109982                 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.');
109983             }
109984
109985             // The headers become child items
109986             me.items = me.columns;
109987             delete me.columns;
109988             delete me.flex;
109989             me.width = 0;
109990
109991             // Acquire initial width from sub headers
109992             for (i = 0, len = me.items.length; i < len; i++) {
109993                 me.width += me.items[i].width || Ext.grid.header.Container.prototype.defaultWidth;
109994                 if (me.items[i].flex) {
109995                     Ext.Error.raise('Ext.grid.column.Column: items of a grouped header do not support flexed values. Each item must explicitly define its width.');
109996                 }
109997             }
109998             me.minWidth = me.width;
109999
110000             me.cls = (me.cls||'') + ' ' + Ext.baseCSSPrefix + 'group-header';
110001             me.sortable = false;
110002             me.fixed = true;
110003             me.align = 'center';
110004         }
110005
110006         Ext.applyIf(me.renderSelectors, {
110007             titleContainer: '.' + Ext.baseCSSPrefix + 'column-header-inner',
110008             triggerEl: '.' + Ext.baseCSSPrefix + 'column-header-trigger',
110009             textEl: '.' + Ext.baseCSSPrefix + 'column-header-text'
110010         });
110011
110012         // Initialize as a HeaderContainer
110013         me.callParent(arguments);
110014     },
110015
110016     onAdd: function(childHeader) {
110017         childHeader.isSubHeader = true;
110018         childHeader.addCls(Ext.baseCSSPrefix + 'group-sub-header');
110019     },
110020
110021     onRemove: function(childHeader) {
110022         childHeader.isSubHeader = false;
110023         childHeader.removeCls(Ext.baseCSSPrefix + 'group-sub-header');
110024     },
110025
110026     initRenderData: function() {
110027         var me = this;
110028         
110029         Ext.applyIf(me.renderData, {
110030             text: me.text,
110031             menuDisabled: me.menuDisabled
110032         });
110033         return me.callParent(arguments);
110034     },
110035
110036     // note that this should invalidate the menu cache
110037     setText: function(text) {
110038         this.text = text;
110039         if (this.rendered) {
110040             this.textEl.update(text);
110041         } 
110042     },
110043
110044     // Find the topmost HeaderContainer: An ancestor which is NOT a Header.
110045     // Group Headers are themselves HeaderContainers
110046     getOwnerHeaderCt: function() {
110047         return this.up(':not([isHeader])');
110048     },
110049
110050     /**
110051      * Returns the true grid column index assiciated with this Column only if this column is a base level Column.
110052      * If it is a group column, it returns <code>false</code>
110053      */
110054     getIndex: function() {
110055         return this.isGroupColumn ? false : this.getOwnerHeaderCt().getHeaderIndex(this);
110056     },
110057
110058     afterRender: function() {
110059         var me = this,
110060             el = me.el;
110061
110062         me.callParent(arguments);
110063
110064         el.addCls(Ext.baseCSSPrefix + 'column-header-align-' + me.align).addClsOnOver(me.overCls);
110065
110066         me.mon(el, {
110067             click:     me.onElClick,
110068             dblclick:  me.onElDblClick,
110069             scope:     me
110070         });
110071         
110072         // BrowserBug: Ie8 Strict Mode, this will break the focus for this browser,
110073         // must be fixed when focus management will be implemented.
110074         if (!Ext.isIE8 || !Ext.isStrict) {
110075             me.mon(me.getFocusEl(), {
110076                 focus: me.onTitleMouseOver,
110077                 blur: me.onTitleMouseOut,
110078                 scope: me
110079             });
110080         }
110081
110082         me.mon(me.titleContainer, {
110083             mouseenter:  me.onTitleMouseOver,
110084             mouseleave:  me.onTitleMouseOut,
110085             scope:      me
110086         });
110087
110088         me.keyNav = Ext.create('Ext.util.KeyNav', el, {
110089             enter: me.onEnterKey,
110090             down: me.onDownKey,
110091             scope: me
110092         });
110093     },
110094
110095     setSize: function(width, height) {
110096         var me = this,
110097             headerCt = me.ownerCt,
110098             ownerHeaderCt = me.getOwnerHeaderCt(),
110099             siblings,
110100             len, i,
110101             oldWidth = me.getWidth(),
110102             newWidth = 0;
110103
110104         if (width !== oldWidth) {
110105
110106             // Bubble size changes upwards to group headers
110107             if (headerCt.isGroupHeader) {
110108
110109                 siblings = headerCt.items.items;
110110                 len = siblings.length;
110111
110112                 // Size the owning group to the size of its sub headers 
110113                 if (siblings[len - 1].rendered) {
110114
110115                     for (i = 0; i < len; i++) {
110116                         newWidth += (siblings[i] === me) ? width : siblings[i].getWidth();
110117                     }
110118                     headerCt.minWidth = newWidth;
110119                     headerCt.setWidth(newWidth);
110120                 }
110121             }
110122             me.callParent(arguments);
110123         }
110124     },
110125
110126     afterComponentLayout: function(width, height) {
110127         var me = this,
110128             ownerHeaderCt = this.getOwnerHeaderCt();
110129
110130         me.callParent(arguments);
110131
110132         // Only changes at the base level inform the grid's HeaderContainer which will update the View
110133         // Skip this if the width is null or undefined which will be the Box layout's initial pass  through the child Components
110134         // Skip this if it's the initial size setting in which case there is no ownerheaderCt yet - that is set afterRender
110135         if (width && !me.isGroupHeader && ownerHeaderCt) {
110136             ownerHeaderCt.onHeaderResize(me, width, true);
110137         }
110138     },
110139
110140     // private
110141     // After the container has laid out and stretched, it calls this to correctly pad the inner to center the text vertically
110142     setPadding: function() {
110143         var me = this,
110144             headerHeight,
110145             lineHeight = parseInt(me.textEl.getStyle('line-height'), 10);
110146
110147         // Top title containing element must stretch to match height of sibling group headers
110148         if (!me.isGroupHeader) {
110149             headerHeight = me.el.getViewSize().height;
110150             if (me.titleContainer.getHeight() < headerHeight) {
110151                 me.titleContainer.dom.style.height = headerHeight + 'px';
110152             }
110153         }
110154         headerHeight = me.titleContainer.getViewSize().height;
110155
110156         // Vertically center the header text in potentially vertically stretched header
110157         if (lineHeight) {
110158             me.titleContainer.setStyle({
110159                 paddingTop: Math.max(((headerHeight - lineHeight) / 2), 0) + 'px'
110160             });
110161         }
110162
110163         // Only IE needs this
110164         if (Ext.isIE && me.triggerEl) {
110165             me.triggerEl.setHeight(headerHeight);
110166         }
110167     },
110168
110169     onDestroy: function() {
110170         var me = this;
110171         Ext.destroy(me.keyNav);
110172         delete me.keyNav;
110173         me.callParent(arguments);
110174     },
110175
110176     onTitleMouseOver: function() {
110177         this.titleContainer.addCls(this.hoverCls);
110178     },
110179
110180     onTitleMouseOut: function() {
110181         this.titleContainer.removeCls(this.hoverCls);
110182     },
110183
110184     onDownKey: function(e) {
110185         if (this.triggerEl) {
110186             this.onElClick(e, this.triggerEl.dom || this.el.dom);
110187         }
110188     },
110189
110190     onEnterKey: function(e) {
110191         this.onElClick(e, this.el.dom);
110192     },
110193
110194     /**
110195      * @private
110196      * Double click 
110197      * @param e
110198      * @param t
110199      */
110200     onElDblClick: function(e, t) {
110201         var me = this,
110202             ownerCt = me.ownerCt;
110203         if (ownerCt && Ext.Array.indexOf(ownerCt.items, me) !== 0 && me.isOnLeftEdge(e) ) {
110204             ownerCt.expandToFit(me.previousSibling('gridcolumn'));
110205         }
110206     },
110207
110208     onElClick: function(e, t) {
110209
110210         // The grid's docked HeaderContainer.
110211         var me = this,
110212             ownerHeaderCt = me.getOwnerHeaderCt();
110213
110214         if (ownerHeaderCt && !ownerHeaderCt.ddLock) {
110215             // Firefox doesn't check the current target in a within check.
110216             // Therefore we check the target directly and then within (ancestors)
110217             if (me.triggerEl && (e.target === me.triggerEl.dom || t === me.triggerEl.dom || e.within(me.triggerEl))) {
110218                 ownerHeaderCt.onHeaderTriggerClick(me, e, t);
110219             // if its not on the left hand edge, sort
110220             } else if (e.getKey() || (!me.isOnLeftEdge(e) && !me.isOnRightEdge(e))) {
110221                 me.toggleSortState();
110222                 ownerHeaderCt.onHeaderClick(me, e, t);
110223             }
110224         }
110225     },
110226
110227     /**
110228      * @private
110229      * Process UI events from the view. The owning TablePanel calls this method, relaying events from the TableView
110230      * @param {String} type Event type, eg 'click'
110231      * @param {TableView} view TableView Component
110232      * @param {HtmlElement} cell Cell HtmlElement the event took place within
110233      * @param {Number} recordIndex Index of the associated Store Model (-1 if none)
110234      * @param {Number} cellIndex Cell index within the row
110235      * @param {EventObject} e Original event
110236      */
110237     processEvent: function(type, view, cell, recordIndex, cellIndex, e) {
110238         return this.fireEvent.apply(this, arguments);
110239     },
110240
110241     toggleSortState: function() {
110242         var me = this,
110243             idx,
110244             nextIdx;
110245             
110246         if (me.sortable) {
110247             idx = Ext.Array.indexOf(me.possibleSortStates, me.sortState);
110248
110249             nextIdx = (idx + 1) % me.possibleSortStates.length;
110250             me.setSortState(me.possibleSortStates[nextIdx]);
110251         }
110252     },
110253
110254     doSort: function(state) {
110255         var ds = this.up('tablepanel').store;
110256         ds.sort({
110257             property: this.getSortParam(),
110258             direction: state
110259         });
110260     },
110261
110262     /**
110263      * Returns the parameter to sort upon when sorting this header. By default
110264      * this returns the dataIndex and will not need to be overriden in most cases.
110265      */
110266     getSortParam: function() {
110267         return this.dataIndex;
110268     },
110269
110270     //setSortState: function(state, updateUI) {
110271     //setSortState: function(state, doSort) {
110272     setSortState: function(state, skipClear, initial) {
110273         var me = this,
110274             colSortClsPrefix = Ext.baseCSSPrefix + 'column-header-sort-',
110275             ascCls = colSortClsPrefix + 'ASC',
110276             descCls = colSortClsPrefix + 'DESC',
110277             nullCls = colSortClsPrefix + 'null',
110278             ownerHeaderCt = me.getOwnerHeaderCt(),
110279             oldSortState = me.sortState;
110280
110281         if (oldSortState !== state && me.getSortParam()) {
110282             me.addCls(colSortClsPrefix + state);
110283             // don't trigger a sort on the first time, we just want to update the UI
110284             if (state && !initial) {
110285                 me.doSort(state);
110286             }
110287             switch (state) {
110288                 case 'DESC':
110289                     me.removeCls([ascCls, nullCls]);
110290                     break;
110291                 case 'ASC':
110292                     me.removeCls([descCls, nullCls]);
110293                     break;
110294                 case null:
110295                     me.removeCls([ascCls, descCls]);
110296                     break;
110297             }
110298             if (ownerHeaderCt && !me.triStateSort && !skipClear) {
110299                 ownerHeaderCt.clearOtherSortStates(me);
110300             }
110301             me.sortState = state;
110302             ownerHeaderCt.fireEvent('sortchange', ownerHeaderCt, me, state);
110303         }
110304     },
110305
110306     hide: function() {
110307         var me = this,
110308             items,
110309             len, i,
110310             lb,
110311             newWidth = 0,
110312             ownerHeaderCt = me.getOwnerHeaderCt();
110313
110314         // Hiding means setting to zero width, so cache the width
110315         me.oldWidth = me.getWidth();
110316
110317         // Hiding a group header hides itself, and then informs the HeaderContainer about its sub headers (Suppressing header layout)
110318         if (me.isGroupHeader) {
110319             items = me.items.items;
110320             me.callParent(arguments);
110321             ownerHeaderCt.onHeaderHide(me);
110322             for (i = 0, len = items.length; i < len; i++) {
110323                 items[i].hidden = true;
110324                 ownerHeaderCt.onHeaderHide(items[i], true);
110325             }
110326             return;
110327         }
110328
110329         // TODO: Work with Jamie to produce a scheme where we can show/hide/resize without triggering a layout cascade
110330         lb = me.ownerCt.componentLayout.layoutBusy;
110331         me.ownerCt.componentLayout.layoutBusy = true;
110332         me.callParent(arguments);
110333         me.ownerCt.componentLayout.layoutBusy = lb;
110334
110335         // Notify owning HeaderContainer
110336         ownerHeaderCt.onHeaderHide(me);
110337
110338         if (me.ownerCt.isGroupHeader) {
110339             // If we've just hidden the last header in a group, then hide the group
110340             items = me.ownerCt.query('>:not([hidden])');
110341             if (!items.length) {
110342                 me.ownerCt.hide();
110343             }
110344             // Size the group down to accommodate fewer sub headers
110345             else {
110346                 for (i = 0, len = items.length; i < len; i++) {
110347                     newWidth += items[i].getWidth();
110348                 }
110349                 me.ownerCt.minWidth = newWidth;
110350                 me.ownerCt.setWidth(newWidth);
110351             }
110352         }
110353     },
110354
110355     show: function() {
110356         var me = this,
110357             ownerCt = me.getOwnerHeaderCt(),
110358             lb,
110359             items,
110360             len, i,
110361             newWidth = 0;
110362
110363         // TODO: Work with Jamie to produce a scheme where we can show/hide/resize without triggering a layout cascade
110364         lb = me.ownerCt.componentLayout.layoutBusy;
110365         me.ownerCt.componentLayout.layoutBusy = true;
110366         me.callParent(arguments);
110367         me.ownerCt.componentLayout.layoutBusy = lb;
110368
110369         // If a sub header, ensure that the group header is visible
110370         if (me.isSubHeader) {
110371             if (!me.ownerCt.isVisible()) {
110372                 me.ownerCt.show();
110373             }
110374         }
110375
110376         // If we've just shown a group with all its sub headers hidden, then show all its sub headers
110377         if (me.isGroupHeader && !me.query(':not([hidden])').length) {
110378             items = me.query('>*');
110379             for (i = 0, len = items.length; i < len; i++) {
110380                 items[i].show();
110381             }
110382         }
110383
110384         // Resize the owning group to accommodate
110385         if (me.ownerCt.isGroupHeader) {
110386             items = me.ownerCt.query('>:not([hidden])');
110387             for (i = 0, len = items.length; i < len; i++) {
110388                 newWidth += items[i].getWidth();
110389             }
110390             me.ownerCt.minWidth = newWidth;
110391             me.ownerCt.setWidth(newWidth);
110392         }
110393
110394         // Notify owning HeaderContainer
110395         if (ownerCt) {
110396             ownerCt.onHeaderShow(me);
110397         }
110398     },
110399
110400     getDesiredWidth: function() {
110401         var me = this;
110402         if (me.rendered && me.componentLayout && me.componentLayout.lastComponentSize) {
110403             // headers always have either a width or a flex
110404             // because HeaderContainer sets a defaults width
110405             // therefore we can ignore the natural width
110406             // we use the componentLayout's tracked width so that
110407             // we can calculate the desired width when rendered
110408             // but not visible because its being obscured by a layout
110409             return me.componentLayout.lastComponentSize.width;
110410         // Flexed but yet to be rendered this could be the case
110411         // where a HeaderContainer and Headers are simply used as data
110412         // structures and not rendered.
110413         }
110414         else if (me.flex) {
110415             // this is going to be wrong, the defaultWidth
110416             return me.width;
110417         }
110418         else {
110419             return me.width;
110420         }
110421     },
110422
110423     getCellSelector: function() {
110424         return '.' + Ext.baseCSSPrefix + 'grid-cell-' + this.getItemId();
110425     },
110426
110427     getCellInnerSelector: function() {
110428         return this.getCellSelector() + ' .' + Ext.baseCSSPrefix + 'grid-cell-inner';
110429     },
110430
110431     isOnLeftEdge: function(e) {
110432         return (e.getXY()[0] - this.el.getLeft() <= this.handleWidth);
110433     },
110434
110435     isOnRightEdge: function(e) {
110436         return (this.el.getRight() - e.getXY()[0] <= this.handleWidth);
110437     }
110438     
110439     /**
110440      * Retrieves the editing field for editing associated with this header. Returns false if there
110441      * is no field associated with the Header the method will return false. If the
110442      * field has not been instantiated it will be created. Note: These methods only has an implementation
110443      * if a Editing plugin has been enabled on the grid.
110444      * @param record The {@link Ext.data.Model Model} instance being edited.
110445      * @param {Mixed} defaultField An object representing a default field to be created
110446      * @returns {Ext.form.field.Field} field
110447      * @method getEditor
110448      */
110449     // intentionally omit getEditor and setEditor definitions bc we applyIf into columns
110450     // when the editing plugin is injected
110451     
110452     
110453     /**
110454      * Sets the form field to be used for editing. Note: This method only has an implementation
110455      * if an Editing plugin has been enabled on the grid.
110456      * @param {Mixed} field An object representing a field to be created. If no xtype is specified a 'textfield' is assumed.
110457      * @method setEditor
110458      */
110459 });
110460 /**
110461  * @class Ext.grid.RowNumberer
110462  * @extends Ext.grid.column.Column
110463  * This is a utility class that can be passed into a {@link Ext.grid.column.Column} as a column config that provides
110464  * an automatic row numbering column.
110465  * <br>Usage:<br><pre><code>
110466 columns: [
110467     Ext.create('Ext.grid.RowNumberer'),
110468     {text: "Company", flex: 1, sortable: true, dataIndex: 'company'},
110469     {text: "Price", width: 120, sortable: true, renderer: Ext.util.Format.usMoney, dataIndex: 'price'},
110470     {text: "Change", width: 120, sortable: true, dataIndex: 'change'},
110471     {text: "% Change", width: 120, sortable: true, dataIndex: 'pctChange'},
110472     {text: "Last Updated", width: 120, sortable: true, renderer: Ext.util.Format.dateRenderer('m/d/Y'), dataIndex: 'lastChange'}
110473 ]
110474  *</code></pre>
110475  * @constructor
110476  * @param {Object} config The configuration options
110477  */
110478 Ext.define('Ext.grid.RowNumberer', {
110479     extend: 'Ext.grid.column.Column',
110480     alias: 'widget.rownumberer',
110481     /**
110482      * @cfg {String} text Any valid text or HTML fragment to display in the header cell for the row
110483      * number column (defaults to '&#160').
110484      */
110485     text: "&#160",
110486
110487     /**
110488      * @cfg {Number} width The default width in pixels of the row number column (defaults to 23).
110489      */
110490     width: 23,
110491
110492     /**
110493      * @cfg {Boolean} sortable True if the row number column is sortable (defaults to false).
110494      * @hide
110495      */
110496     sortable: false,
110497
110498     align: 'right',
110499
110500     constructor : function(config){
110501         this.callParent(arguments);
110502         if (this.rowspan) {
110503             this.renderer = Ext.Function.bind(this.renderer, this);
110504         }
110505     },
110506
110507     // private
110508     fixed: true,
110509     hideable: false,
110510     menuDisabled: true,
110511     dataIndex: '',
110512     cls: Ext.baseCSSPrefix + 'row-numberer',
110513     rowspan: undefined,
110514
110515     // private
110516     renderer: function(value, metaData, record, rowIdx, colIdx, store) {
110517         if (this.rowspan){
110518             metaData.cellAttr = 'rowspan="'+this.rowspan+'"';
110519         }
110520
110521         metaData.tdCls = Ext.baseCSSPrefix + 'grid-cell-special';
110522         return store.indexOfTotal(record) + 1;
110523     }
110524 });
110525
110526 /**
110527  * @class Ext.view.DropZone
110528  * @extends Ext.dd.DropZone
110529  * @private
110530  */
110531 Ext.define('Ext.view.DropZone', {
110532     extend: 'Ext.dd.DropZone',
110533
110534     indicatorHtml: '<div class="x-grid-drop-indicator-left"></div><div class="x-grid-drop-indicator-right"></div>',
110535     indicatorCls: 'x-grid-drop-indicator',
110536
110537     constructor: function(config) {
110538         var me = this;
110539         Ext.apply(me, config);
110540
110541         // Create a ddGroup unless one has been configured.
110542         // User configuration of ddGroups allows users to specify which
110543         // DD instances can interact with each other. Using one
110544         // based on the id of the View would isolate it and mean it can only
110545         // interact with a DragZone on the same View also using a generated ID.
110546         if (!me.ddGroup) {
110547             me.ddGroup = 'view-dd-zone-' + me.view.id;
110548         }
110549
110550         // The DropZone's encapsulating element is the View's main element. It must be this because drop gestures
110551         // may require scrolling on hover near a scrolling boundary. In Ext 4.x two DD instances may not use the
110552         // same element, so a DragZone on this same View must use the View's parent element as its element.
110553         me.callParent([me.view.el]);
110554     },
110555
110556 //  Fire an event through the client DataView. Lock this DropZone during the event processing so that
110557 //  its data does not become corrupted by processing mouse events.
110558     fireViewEvent: function() {
110559         this.lock();
110560         var result = this.view.fireEvent.apply(this.view, arguments);
110561         this.unlock();
110562         return result;
110563     },
110564
110565     getTargetFromEvent : function(e) {
110566         var node = e.getTarget(this.view.getItemSelector()),
110567             mouseY, nodeList, testNode, i, len, box;
110568
110569 //      Not over a row node: The content may be narrower than the View's encapsulating element, so return the closest.
110570 //      If we fall through because the mouse is below the nodes (or there are no nodes), we'll get an onContainerOver call.
110571         if (!node) {
110572             mouseY = e.getPageY();
110573             for (i = 0, nodeList = this.view.getNodes(), len = nodeList.length; i < len; i++) {
110574                 testNode = nodeList[i];
110575                 box = Ext.fly(testNode).getBox();
110576                 if (mouseY <= box.bottom) {
110577                     return testNode;
110578                 }
110579             }
110580         }
110581         return node;
110582     },
110583
110584     getIndicator: function() {
110585         var me = this;
110586
110587         if (!me.indicator) {
110588             me.indicator = Ext.createWidget('component', {
110589                 html: me.indicatorHtml,
110590                 cls: me.indicatorCls,
110591                 ownerCt: me.view,
110592                 floating: true,
110593                 shadow: false
110594             });
110595         }
110596         return me.indicator;
110597     },
110598
110599     getPosition: function(e, node) {
110600         var y      = e.getXY()[1],
110601             region = Ext.fly(node).getRegion(),
110602             pos;
110603
110604         if ((region.bottom - y) >= (region.bottom - region.top) / 2) {
110605             pos = "before";
110606         } else {
110607             pos = "after";
110608         }
110609         return pos;
110610     },
110611
110612     /**
110613      * @private Determines whether the record at the specified offset from the passed record
110614      * is in the drag payload.
110615      * @param records
110616      * @param record
110617      * @param offset
110618      * @returns {Boolean} True if the targeted record is in the drag payload
110619      */
110620     containsRecordAtOffset: function(records, record, offset) {
110621         if (!record) {
110622             return false;
110623         }
110624         var view = this.view,
110625             recordIndex = view.indexOf(record),
110626             nodeBefore = view.getNode(recordIndex + offset),
110627             recordBefore = nodeBefore ? view.getRecord(nodeBefore) : null;
110628
110629         return recordBefore && Ext.Array.contains(records, recordBefore);
110630     },
110631
110632     positionIndicator: function(node, data, e) {
110633         var me = this,
110634             view = me.view,
110635             pos = me.getPosition(e, node),
110636             overRecord = view.getRecord(node),
110637             draggingRecords = data.records,
110638             indicator, indicatorY;
110639
110640         if (!Ext.Array.contains(draggingRecords, overRecord) && (
110641             pos == 'before' && !me.containsRecordAtOffset(draggingRecords, overRecord, -1) ||
110642             pos == 'after' && !me.containsRecordAtOffset(draggingRecords, overRecord, 1)
110643         )) {
110644             me.valid = true;
110645
110646             if (me.overRecord != overRecord || me.currentPosition != pos) {
110647
110648                 indicatorY = Ext.fly(node).getY() - view.el.getY() - 1;
110649                 if (pos == 'after') {
110650                     indicatorY += Ext.fly(node).getHeight();
110651                 }
110652                 me.getIndicator().setWidth(Ext.fly(view.el).getWidth()).showAt(0, indicatorY);
110653
110654                 // Cache the overRecord and the 'before' or 'after' indicator.
110655                 me.overRecord = overRecord;
110656                 me.currentPosition = pos;
110657             }
110658         } else {
110659             me.invalidateDrop();
110660         }
110661     },
110662
110663     invalidateDrop: function() {
110664         if (this.valid) {
110665             this.valid = false;
110666             this.getIndicator().hide();
110667         }
110668     },
110669
110670     // The mouse is over a View node
110671     onNodeOver: function(node, dragZone, e, data) {
110672         if (!Ext.Array.contains(data.records, this.view.getRecord(node))) {
110673             this.positionIndicator(node, data, e);
110674         }
110675         return this.valid ? this.dropAllowed : this.dropNotAllowed;
110676     },
110677
110678     // Moved out of the DropZone without dropping.
110679     // Remove drop position indicator
110680     notifyOut: function(node, dragZone, e, data) {
110681         this.callParent(arguments);
110682         delete this.overRecord;
110683         delete this.currentPosition;
110684         if (this.indicator) {
110685             this.indicator.hide();
110686         }
110687     },
110688
110689     // The mouse is past the end of all nodes (or there are no nodes)
110690     onContainerOver : function(dd, e, data) {
110691         var v = this.view,
110692             c = v.store.getCount();
110693
110694         // There are records, so position after the last one
110695         if (c) {
110696             this.positionIndicator(v.getNode(c - 1), data, e);
110697         }
110698
110699         // No records, position the indicator at the top
110700         else {
110701             delete this.overRecord;
110702             delete this.currentPosition;
110703             this.getIndicator().setWidth(Ext.fly(v.el).getWidth()).showAt(0, 0);
110704             this.valid = true;
110705         }
110706         return this.dropAllowed;
110707     },
110708
110709     onContainerDrop : function(dd, e, data) {
110710         return this.onNodeDrop(dd, null, e, data);
110711     },
110712
110713     onNodeDrop: function(node, dragZone, e, data) {
110714         var me = this,
110715             dropped = false,
110716
110717             // Create a closure to perform the operation which the event handler may use.
110718             // Users may now return <code>0</code> from the beforedrop handler, and perform any kind
110719             // of asynchronous processing such as an Ext.Msg.confirm, or an Ajax request,
110720             // and complete the drop gesture at some point in the future by calling this function.
110721             processDrop = function () {
110722                 me.invalidateDrop();
110723                 me.handleNodeDrop(data, me.overRecord, me.currentPosition);
110724                 dropped = true;
110725                 me.fireViewEvent('drop', node, data, me.overRecord, me.currentPosition);
110726             },
110727             performOperation;
110728
110729         if (me.valid) {
110730             performOperation = me.fireViewEvent('beforedrop', node, data, me.overRecord, me.currentPosition, processDrop);
110731             if (performOperation === 0) {
110732                 return;
110733             } else if (performOperation !== false) {
110734                 // If the processDrop function was called in the event handler, do not do it again.
110735                 if (!dropped) {
110736                     processDrop();
110737                 }
110738             } else {
110739                 return false;
110740             }
110741         } else {
110742             return false;
110743         }
110744     }
110745 });
110746
110747 Ext.define('Ext.grid.ViewDropZone', {
110748     extend: 'Ext.view.DropZone',
110749
110750     indicatorHtml: '<div class="x-grid-drop-indicator-left"></div><div class="x-grid-drop-indicator-right"></div>',
110751     indicatorCls: 'x-grid-drop-indicator',
110752
110753     handleNodeDrop : function(data, record, position) {
110754         var view = this.view,
110755             store = view.getStore(),
110756             index, records, i, len;
110757
110758         // If the copy flag is set, create a copy of the Models with the same IDs
110759         if (data.copy) {
110760             records = data.records;
110761             data.records = [];
110762             for (i = 0, len = records.length; i < len; i++) {
110763                 data.records.push(records[i].copy(records[i].getId()));
110764             }
110765         } else {
110766             /*
110767              * Remove from the source store. We do this regardless of whether the store
110768              * is the same bacsue the store currently doesn't handle moving records
110769              * within the store. In the future it should be possible to do this.
110770              * Here was pass the isMove parameter if we're moving to the same view.
110771              */
110772             data.view.store.remove(data.records, data.view === view);
110773         }
110774
110775         index = store.indexOf(record);
110776         if (position == 'after') {
110777             index++;
110778         }
110779         store.insert(index, data.records);
110780         view.getSelectionModel().select(data.records);
110781     }
110782 });
110783 /**
110784  * @class Ext.grid.column.Action
110785  * @extends Ext.grid.column.Column
110786  * <p>A Grid header type which renders an icon, or a series of icons in a grid cell, and offers a scoped click
110787  * handler for each icon.</p>
110788  *
110789  * {@img Ext.grid.column.Action/Ext.grid.column.Action.png Ext.grid.column.Action grid column}
110790  *  
110791  * ## Code
110792  *     Ext.create('Ext.data.Store', {
110793  *         storeId:'employeeStore',
110794  *         fields:['firstname', 'lastname', 'senority', 'dep', 'hired'],
110795  *         data:[
110796  *             {firstname:"Michael", lastname:"Scott"},
110797  *             {firstname:"Dwight", lastname:"Schrute"},
110798  *             {firstname:"Jim", lastname:"Halpert"},
110799  *             {firstname:"Kevin", lastname:"Malone"},
110800  *             {firstname:"Angela", lastname:"Martin"}                        
110801  *         ]
110802  *     });
110803  *     
110804  *     Ext.create('Ext.grid.Panel', {
110805  *         title: 'Action Column Demo',
110806  *         store: Ext.data.StoreManager.lookup('employeeStore'),
110807  *         columns: [
110808  *             {text: 'First Name',  dataIndex:'firstname'},
110809  *             {text: 'Last Name',  dataIndex:'lastname'},
110810  *             {
110811  *                 xtype:'actioncolumn', 
110812  *                 width:50,
110813  *                 items: [{
110814  *                     icon: 'images/edit.png',  // Use a URL in the icon config
110815  *                     tooltip: 'Edit',
110816  *                     handler: function(grid, rowIndex, colIndex) {
110817  *                         var rec = grid.getStore().getAt(rowIndex);
110818  *                         alert("Edit " + rec.get('firstname'));
110819  *                     }
110820  *                 },{
110821  *                     icon: 'images/delete.png',
110822  *                     tooltip: 'Delete',
110823  *                     handler: function(grid, rowIndex, colIndex) {
110824  *                         var rec = grid.getStore().getAt(rowIndex);
110825  *                         alert("Terminate " + rec.get('firstname'));
110826  *                     }                
110827  *                 }]
110828  *             }
110829  *         ],
110830  *         width: 250,
110831  *         renderTo: Ext.getBody()
110832  *     });
110833  * <p>The action column can be at any index in the columns array, and a grid can have any number of
110834  * action columns. </p>
110835  * @xtype actioncolumn
110836  */
110837 Ext.define('Ext.grid.column.Action', {
110838     extend: 'Ext.grid.column.Column',
110839     alias: ['widget.actioncolumn'],
110840     alternateClassName: 'Ext.grid.ActionColumn',
110841
110842     /**
110843      * @cfg {String} icon
110844      * The URL of an image to display as the clickable element in the column. 
110845      * Optional - defaults to <code>{@link Ext#BLANK_IMAGE_URL Ext.BLANK_IMAGE_URL}</code>.
110846      */
110847     /**
110848      * @cfg {String} iconCls
110849      * A CSS class to apply to the icon image. To determine the class dynamically, configure the Column with a <code>{@link #getClass}</code> function.
110850      */
110851     /**
110852      * @cfg {Function} handler A function called when the icon is clicked.
110853      * The handler is passed the following parameters:<div class="mdetail-params"><ul>
110854      * <li><code>view</code> : TableView<div class="sub-desc">The owning TableView.</div></li>
110855      * <li><code>rowIndex</code> : Number<div class="sub-desc">The row index clicked on.</div></li>
110856      * <li><code>colIndex</code> : Number<div class="sub-desc">The column index clicked on.</div></li>
110857      * <li><code>item</code> : Object<div class="sub-desc">The clicked item (or this Column if multiple 
110858      * {@link #items} were not configured).</div></li>
110859      * <li><code>e</code> : Event<div class="sub-desc">The click event.</div></li>
110860      * </ul></div>
110861      */
110862     /**
110863      * @cfg {Object} scope The scope (<tt><b>this</b></tt> reference) in which the <code>{@link #handler}</code>
110864      * and <code>{@link #getClass}</code> fuctions are executed. Defaults to this Column.
110865      */
110866     /**
110867      * @cfg {String} tooltip A tooltip message to be displayed on hover. {@link Ext.tip.QuickTipManager#init Ext.tip.QuickTipManager} must have 
110868      * been initialized.
110869      */
110870     /**
110871      * @cfg {Boolean} stopSelection Defaults to <code>true</code>. Prevent grid <i>row</i> selection upon mousedown.
110872      */
110873     /**
110874      * @cfg {Function} getClass A function which returns the CSS class to apply to the icon image.
110875      * The function is passed the following parameters:<ul>
110876      *     <li><b>v</b> : Object<p class="sub-desc">The value of the column's configured field (if any).</p></li>
110877      *     <li><b>metadata</b> : Object<p class="sub-desc">An object in which you may set the following attributes:<ul>
110878      *         <li><b>css</b> : String<p class="sub-desc">A CSS class name to add to the cell's TD element.</p></li>
110879      *         <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
110880      *         (e.g. 'style="color:red;"').</p></li>
110881      *     </ul></p></li>
110882      *     <li><b>r</b> : Ext.data.Record<p class="sub-desc">The Record providing the data.</p></li>
110883      *     <li><b>rowIndex</b> : Number<p class="sub-desc">The row index..</p></li>
110884      *     <li><b>colIndex</b> : Number<p class="sub-desc">The column index.</p></li>
110885      *     <li><b>store</b> : Ext.data.Store<p class="sub-desc">The Store which is providing the data Model.</p></li>
110886      * </ul>
110887      */
110888     /**
110889      * @cfg {Array} items An Array which may contain multiple icon definitions, each element of which may contain:
110890      * <div class="mdetail-params"><ul>
110891      * <li><code>icon</code> : String<div class="sub-desc">The url of an image to display as the clickable element 
110892      * in the column.</div></li>
110893      * <li><code>iconCls</code> : String<div class="sub-desc">A CSS class to apply to the icon image.
110894      * To determine the class dynamically, configure the item with a <code>getClass</code> function.</div></li>
110895      * <li><code>getClass</code> : Function<div class="sub-desc">A function which returns the CSS class to apply to the icon image.
110896      * The function is passed the following parameters:<ul>
110897      *     <li><b>v</b> : Object<p class="sub-desc">The value of the column's configured field (if any).</p></li>
110898      *     <li><b>metadata</b> : Object<p class="sub-desc">An object in which you may set the following attributes:<ul>
110899      *         <li><b>css</b> : String<p class="sub-desc">A CSS class name to add to the cell's TD element.</p></li>
110900      *         <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
110901      *         (e.g. 'style="color:red;"').</p></li>
110902      *     </ul></p></li>
110903      *     <li><b>r</b> : Ext.data.Record<p class="sub-desc">The Record providing the data.</p></li>
110904      *     <li><b>rowIndex</b> : Number<p class="sub-desc">The row index..</p></li>
110905      *     <li><b>colIndex</b> : Number<p class="sub-desc">The column index.</p></li>
110906      *     <li><b>store</b> : Ext.data.Store<p class="sub-desc">The Store which is providing the data Model.</p></li>
110907      * </ul></div></li>
110908      * <li><code>handler</code> : Function<div class="sub-desc">A function called when the icon is clicked.</div></li>
110909      * <li><code>scope</code> : Scope<div class="sub-desc">The scope (<code><b>this</b></code> reference) in which the 
110910      * <code>handler</code> and <code>getClass</code> functions are executed. Fallback defaults are this Column's
110911      * configured scope, then this Column.</div></li>
110912      * <li><code>tooltip</code> : String<div class="sub-desc">A tooltip message to be displayed on hover. 
110913      * {@link Ext.tip.QuickTipManager#init Ext.tip.QuickTipManager} must have been initialized.</div></li>
110914      * </ul></div>
110915      */
110916     header: '&#160;',
110917
110918     actionIdRe: /x-action-col-(\d+)/,
110919
110920     /**
110921      * @cfg {String} altText The alt text to use for the image element. Defaults to <tt>''</tt>.
110922      */
110923     altText: '',
110924     
110925     sortable: false,
110926
110927     constructor: function(config) {
110928         var me = this,
110929             cfg = Ext.apply({}, config),
110930             items = cfg.items || [me],
110931             l = items.length,
110932             i,
110933             item;
110934
110935         // This is a Container. Delete the items config to be reinstated after construction.
110936         delete cfg.items;
110937         this.callParent([cfg]);
110938
110939         // Items is an array property of ActionColumns
110940         me.items = items;
110941
110942 //      Renderer closure iterates through items creating an <img> element for each and tagging with an identifying 
110943 //      class name x-action-col-{n}
110944         me.renderer = function(v, meta) {
110945 //          Allow a configured renderer to create initial value (And set the other values in the "metadata" argument!)
110946             v = Ext.isFunction(cfg.renderer) ? cfg.renderer.apply(this, arguments)||'' : '';
110947
110948             meta.tdCls += ' ' + Ext.baseCSSPrefix + 'action-col-cell';
110949             for (i = 0; i < l; i++) {
110950                 item = items[i];
110951                 v += '<img alt="' + me.altText + '" src="' + (item.icon || Ext.BLANK_IMAGE_URL) +
110952                     '" class="' + Ext.baseCSSPrefix + 'action-col-icon ' + Ext.baseCSSPrefix + 'action-col-' + String(i) + ' ' +  (item.iconCls || '') + 
110953                     ' ' + (Ext.isFunction(item.getClass) ? item.getClass.apply(item.scope||me.scope||me, arguments) : (me.iconCls || '')) + '"' +
110954                     ((item.tooltip) ? ' data-qtip="' + item.tooltip + '"' : '') + ' />';
110955             }
110956             return v;
110957         };
110958     },
110959
110960     destroy: function() {
110961         delete this.items;
110962         delete this.renderer;
110963         return this.callParent(arguments);
110964     },
110965
110966     /**
110967      * @private
110968      * Process and refire events routed from the GridView's processEvent method.
110969      * Also fires any configured click handlers. By default, cancels the mousedown event to prevent selection.
110970      * Returns the event handler's status to allow canceling of GridView's bubbling process.
110971      */
110972     processEvent : function(type, view, cell, recordIndex, cellIndex, e){
110973         var m = e.getTarget().className.match(this.actionIdRe),
110974             item, fn;
110975         if (m && (item = this.items[parseInt(m[1], 10)])) {
110976             if (type == 'click') {
110977                 fn = item.handler;
110978                 if (fn || this.handler) {
110979                     fn.call(item.scope||this.scope||this, view, recordIndex, cellIndex, item, e);
110980                 }
110981             } else if ((type == 'mousedown') && (item.stopSelection !== false)) {
110982                 return false;
110983             }
110984         }
110985         return this.callParent(arguments);
110986     },
110987
110988     cascade: function(fn, scope) {
110989         fn.call(scope||this, this);
110990     },
110991
110992     // Private override because this cannot function as a Container, and it has an items property which is an Array, NOT a MixedCollection.
110993     getRefItems: function() {
110994         return [];
110995     }
110996 });
110997 /**
110998  * @class Ext.grid.column.Boolean
110999  * @extends Ext.grid.column.Column
111000  * <p>A Column definition class which renders boolean data fields.  See the {@link Ext.grid.column.Column#xtype xtype}
111001  * config option of {@link Ext.grid.column.Column} for more details.</p>
111002  *
111003  * {@img Ext.grid.column.Boolean/Ext.grid.column.Boolean.png Ext.grid.column.Boolean grid column}
111004  *
111005  *  ## Code
111006  *     Ext.create('Ext.data.Store', {
111007  *        storeId:'sampleStore',
111008  *        fields:[
111009  *            {name: 'framework', type: 'string'},
111010  *            {name: 'rocks', type: 'boolean'}
111011  *        ],
111012  *        data:{'items':[
111013  *            {"framework":"Ext JS 4", "rocks":true},
111014  *            {"framework":"Sencha Touch", "rocks":true},
111015  *            {"framework":"Ext GWT", "rocks":true},            
111016  *            {"framework":"Other Guys", "rocks":false}            
111017  *        ]},
111018  *        proxy: {
111019  *            type: 'memory',
111020  *            reader: {
111021  *                type: 'json',
111022  *                root: 'items'
111023  *            }
111024  *        }
111025  *    });
111026  *    
111027  *    Ext.create('Ext.grid.Panel', {
111028  *        title: 'Boolean Column Demo',
111029  *        store: Ext.data.StoreManager.lookup('sampleStore'),
111030  *        columns: [
111031  *            {text: 'Framework',  dataIndex: 'framework', flex: 1},
111032  *            {
111033  *                xtype: 'booleancolumn', 
111034  *                text: 'Rocks',
111035  *                trueText: 'Yes',
111036  *                falseText: 'No', 
111037  *                dataIndex: 'rocks'}
111038  *        ],
111039  *        height: 200,
111040  *        width: 400,
111041  *        renderTo: Ext.getBody()
111042  *    });
111043  * 
111044  * @xtype booleancolumn
111045  */
111046 Ext.define('Ext.grid.column.Boolean', {
111047     extend: 'Ext.grid.column.Column',
111048     alias: ['widget.booleancolumn'],
111049     alternateClassName: 'Ext.grid.BooleanColumn',
111050
111051     /**
111052      * @cfg {String} trueText
111053      * The string returned by the renderer when the column value is not falsey (defaults to <tt>'true'</tt>).
111054      */
111055     trueText: 'true',
111056
111057     /**
111058      * @cfg {String} falseText
111059      * The string returned by the renderer when the column value is falsey (but not undefined) (defaults to
111060      * <tt>'false'</tt>).
111061      */
111062     falseText: 'false',
111063
111064     /**
111065      * @cfg {String} undefinedText
111066      * The string returned by the renderer when the column value is undefined (defaults to <tt>'&#160;'</tt>).
111067      */
111068     undefinedText: '&#160;',
111069
111070     constructor: function(cfg){
111071         this.callParent(arguments);
111072         var trueText      = this.trueText,
111073             falseText     = this.falseText,
111074             undefinedText = this.undefinedText;
111075
111076         this.renderer = function(value){
111077             if(value === undefined){
111078                 return undefinedText;
111079             }
111080             if(!value || value === 'false'){
111081                 return falseText;
111082             }
111083             return trueText;
111084         };
111085     }
111086 });
111087 /**
111088  * @class Ext.grid.column.Date
111089  * @extends Ext.grid.column.Column
111090  * <p>A Column definition class which renders a passed date according to the default locale, or a configured
111091  * {@link #format}.</p>
111092  *
111093  * {@img Ext.grid.column.Date/Ext.grid.column.Date.png Ext.grid.column.Date grid column}
111094  *
111095  * ## Code
111096  *    Ext.create('Ext.data.Store', {
111097  *        storeId:'sampleStore',
111098  *        fields:[
111099  *            {name: 'symbol', type: 'string'},
111100  *            {name: 'date', type: 'date'},
111101  *            {name: 'change', type: 'number'},
111102  *            {name: 'volume', type: 'number'},
111103  *            {name: 'topday', type: 'date'}                        
111104  *        ],
111105  *        data:[
111106  *            {symbol:"msft", date:'2011/04/22', change:2.43, volume:61606325, topday:'04/01/2010'},
111107  *            {symbol:"goog", date:'2011/04/22', change:0.81, volume:3053782, topday:'04/11/2010'},
111108  *            {symbol:"apple", date:'2011/04/22', change:1.35, volume:24484858, topday:'04/28/2010'},            
111109  *            {symbol:"sencha", date:'2011/04/22', change:8.85, volume:5556351, topday:'04/22/2010'}            
111110  *        ]
111111  *    });
111112  *    
111113  *    Ext.create('Ext.grid.Panel', {
111114  *        title: 'Date Column Demo',
111115  *        store: Ext.data.StoreManager.lookup('sampleStore'),
111116  *        columns: [
111117  *            {text: 'Symbol',  dataIndex: 'symbol', flex: 1},
111118  *            {text: 'Date',  dataIndex: 'date', xtype: 'datecolumn', format:'Y-m-d'},
111119  *            {text: 'Change',  dataIndex: 'change', xtype: 'numbercolumn', format:'0.00'},
111120  *            {text: 'Volume',  dataIndex: 'volume', xtype: 'numbercolumn', format:'0,000'},
111121  *            {text: 'Top Day',  dataIndex: 'topday', xtype: 'datecolumn', format:'l'}            
111122  *        ],
111123  *        height: 200,
111124  *        width: 450,
111125  *        renderTo: Ext.getBody()
111126  *    });
111127  *    
111128  * @xtype datecolumn
111129  */
111130 Ext.define('Ext.grid.column.Date', {
111131     extend: 'Ext.grid.column.Column',
111132     alias: ['widget.datecolumn'],
111133     requires: ['Ext.Date'],
111134     alternateClassName: 'Ext.grid.DateColumn',
111135
111136     /**
111137      * @cfg {String} format
111138      * A formatting string as used by {@link Date#format Date.format} to format a Date for this Column.
111139      * This defaults to the default date from {@link Ext.Date#defaultFormat} which itself my be overridden
111140      * in a locale file.
111141      */
111142     format : Ext.Date.defaultFormat,
111143
111144     constructor: function(cfg){
111145         this.callParent(arguments);
111146         this.renderer = Ext.util.Format.dateRenderer(this.format);
111147     }
111148 });
111149 /**
111150  * @class Ext.grid.column.Number
111151  * @extends Ext.grid.column.Column
111152  * <p>A Column definition class which renders a numeric data field according to a {@link #format} string.</p>
111153  *
111154  * {@img Ext.grid.column.Number/Ext.grid.column.Number.png Ext.grid.column.Number cell editing}
111155  *
111156  * ## Code
111157  *     Ext.create('Ext.data.Store', {
111158  *        storeId:'sampleStore',
111159  *        fields:[
111160  *            {name: 'symbol', type: 'string'},
111161  *            {name: 'price', type: 'number'},
111162  *            {name: 'change', type: 'number'},
111163  *            {name: 'volume', type: 'number'},            
111164  *        ],
111165  *        data:[
111166  *            {symbol:"msft", price:25.76, change:2.43, volume:61606325},
111167  *            {symbol:"goog", price:525.73, change:0.81, volume:3053782},
111168  *            {symbol:"apple", price:342.41, change:1.35, volume:24484858},            
111169  *            {symbol:"sencha", price:142.08, change:8.85, volume:5556351}            
111170  *        ]
111171  *    });
111172  *    
111173  *    Ext.create('Ext.grid.Panel', {
111174  *        title: 'Number Column Demo',
111175  *        store: Ext.data.StoreManager.lookup('sampleStore'),
111176  *        columns: [
111177  *            {text: 'Symbol',  dataIndex: 'symbol', flex: 1},
111178  *            {text: 'Current Price',  dataIndex: 'price', renderer: Ext.util.Format.usMoney},
111179  *            {text: 'Change',  dataIndex: 'change', xtype: 'numbercolumn', format:'0.00'},
111180  *            {text: 'Volume',  dataIndex: 'volume', xtype: 'numbercolumn', format:'0,000'}
111181  *        ],
111182  *        height: 200,
111183  *        width: 400,
111184  *        renderTo: Ext.getBody()
111185  *    });
111186  * 
111187  * @xtype numbercolumn
111188  */
111189 Ext.define('Ext.grid.column.Number', {
111190     extend: 'Ext.grid.column.Column',
111191     alias: ['widget.numbercolumn'],
111192     requires: ['Ext.util.Format'],
111193     alternateClassName: 'Ext.grid.NumberColumn',
111194
111195     /**
111196      * @cfg {String} format
111197      * A formatting string as used by {@link Ext.util.Format#number} to format a numeric value for this Column
111198      * (defaults to <code>'0,000.00'</code>).
111199      */
111200     format : '0,000.00',
111201     constructor: function(cfg) {
111202         this.callParent(arguments);
111203         this.renderer = Ext.util.Format.numberRenderer(this.format);
111204     }
111205 });
111206 /**
111207  * @class Ext.grid.column.Template
111208  * @extends Ext.grid.column.Column
111209  * 
111210  * A Column definition class which renders a value by processing a {@link Ext.data.Model Model}'s
111211  * {@link Ext.data.Model#data data} using a {@link #tpl configured} {@link Ext.XTemplate XTemplate}.
111212  * 
111213  *  {@img Ext.grid.column.Template/Ext.grid.column.Template.png Ext.grid.column.Template grid column}
111214  * 
111215  * ## Code
111216  *     Ext.create('Ext.data.Store', {
111217  *         storeId:'employeeStore',
111218  *         fields:['firstname', 'lastname', 'senority', 'department'],
111219  *         groupField: 'department',
111220  *         data:[
111221  *             {firstname:"Michael", lastname:"Scott", senority:7, department:"Manangement"},
111222  *             {firstname:"Dwight", lastname:"Schrute", senority:2, department:"Sales"},
111223  *             {firstname:"Jim", lastname:"Halpert", senority:3, department:"Sales"},
111224  *             {firstname:"Kevin", lastname:"Malone", senority:4, department:"Accounting"},
111225  *             {firstname:"Angela", lastname:"Martin", senority:5, department:"Accounting"}                        
111226  *         ]
111227  *     });
111228  *     
111229  *     Ext.create('Ext.grid.Panel', {
111230  *         title: 'Column Template Demo',
111231  *         store: Ext.data.StoreManager.lookup('employeeStore'),
111232  *         columns: [
111233  *             {text: 'Full Name',  xtype:'templatecolumn', tpl:'{firstname} {lastname}', flex:1},
111234  *             {text: 'Deparment (Yrs)', xtype:'templatecolumn', tpl:'{department} ({senority})'}
111235  *         ],
111236  *         height: 200,
111237  *         width: 300,
111238  *         renderTo: Ext.getBody()
111239  *     });
111240  * 
111241  * @markdown
111242  * @xtype templatecolumn
111243  */
111244 Ext.define('Ext.grid.column.Template', {
111245     extend: 'Ext.grid.column.Column',
111246     alias: ['widget.templatecolumn'],
111247     requires: ['Ext.XTemplate'],
111248     alternateClassName: 'Ext.grid.TemplateColumn',
111249
111250     /**
111251      * @cfg {String/XTemplate} tpl
111252      * An {@link Ext.XTemplate XTemplate}, or an XTemplate <i>definition string</i> to use to process a
111253      * {@link Ext.data.Model Model}'s {@link Ext.data.Model#data data} to produce a column's rendered value.
111254      */
111255     constructor: function(cfg){
111256         var me = this,
111257             tpl;
111258             
111259         me.callParent(arguments);
111260         tpl = me.tpl = (!Ext.isPrimitive(me.tpl) && me.tpl.compile) ? me.tpl : Ext.create('Ext.XTemplate', me.tpl);
111261
111262         me.renderer = function(value, p, record) {
111263             var data = Ext.apply({}, record.data, record.getAssociatedData());
111264             return tpl.apply(data);
111265         };
111266     }
111267 });
111268
111269 /**
111270  * @class Ext.grid.feature.Feature
111271  * @extends Ext.util.Observable
111272  * 
111273  * A feature is a type of plugin that is specific to the {@link Ext.grid.Panel}. It provides several
111274  * hooks that allows the developer to inject additional functionality at certain points throughout the 
111275  * grid creation cycle. This class provides the base template methods that are available to the developer,
111276  * it should be extended.
111277  * 
111278  * There are several built in features that extend this class, for example:
111279  *
111280  *  - {@link Ext.grid.feature.Grouping} - Shows grid rows in groups as specified by the {@link Ext.data.Store}
111281  *  - {@link Ext.grid.feature.RowBody} - Adds a body section for each grid row that can contain markup.
111282  *  - {@link Ext.grid.feature.Summary} - Adds a summary row at the bottom of the grid with aggregate totals for a column.
111283  * 
111284  * ## Using Features
111285  * A feature is added to the grid by specifying it an array of features in the configuration:
111286  * 
111287  *     var groupingFeature = Ext.create('Ext.grid.feature.Grouping');
111288  *     Ext.create('Ext.grid.Panel', {
111289  *         // other options
111290  *         features: [groupingFeature]
111291  *     });
111292  * 
111293  * @abstract
111294  */
111295 Ext.define('Ext.grid.feature.Feature', {
111296     extend: 'Ext.util.Observable',
111297     alias: 'feature.feature',
111298     
111299     isFeature: true,
111300     disabled: false,
111301     
111302     /**
111303      * @property {Boolean}
111304      * Most features will expose additional events, some may not and will
111305      * need to change this to false.
111306      */
111307     hasFeatureEvent: true,
111308     
111309     /**
111310      * @property {String}
111311      * Prefix to use when firing events on the view.
111312      * For example a prefix of group would expose "groupclick", "groupcontextmenu", "groupdblclick".
111313      */
111314     eventPrefix: null,
111315     
111316     /**
111317      * @property {String}
111318      * Selector used to determine when to fire the event with the eventPrefix.
111319      */
111320     eventSelector: null,
111321     
111322     /**
111323      * @property {Ext.view.Table}
111324      * Reference to the TableView.
111325      */
111326     view: null,
111327     
111328     /**
111329      * @property {Ext.grid.Panel}
111330      * Reference to the grid panel
111331      */
111332     grid: null,
111333     
111334     /**
111335      * Most features will not modify the data returned to the view.
111336      * This is limited to one feature that manipulates the data per grid view.
111337      */
111338     collectData: false,
111339         
111340     getFeatureTpl: function() {
111341         return '';
111342     },
111343     
111344     /**
111345      * Abstract method to be overriden when a feature should add additional
111346      * arguments to its event signature. By default the event will fire:
111347      * - view - The underlying Ext.view.Table
111348      * - featureTarget - The matched element by the defined {@link eventSelector}
111349      *
111350      * The method must also return the eventName as the first index of the array
111351      * to be passed to fireEvent.
111352      */
111353     getFireEventArgs: function(eventName, view, featureTarget) {
111354         return [eventName, view, featureTarget];
111355     },
111356     
111357     /**
111358      * Approriate place to attach events to the view, selectionmodel, headerCt, etc
111359      */
111360     attachEvents: function() {
111361         
111362     },
111363     
111364     getFragmentTpl: function() {
111365         return;
111366     },
111367     
111368     /**
111369      * Allows a feature to mutate the metaRowTpl.
111370      * The array received as a single argument can be manipulated to add things
111371      * on the end/begining of a particular row.
111372      */
111373     mutateMetaRowTpl: function(metaRowTplArray) {
111374         
111375     },
111376     
111377     /**
111378      * Allows a feature to inject member methods into the metaRowTpl. This is
111379      * important for embedding functionality which will become part of the proper
111380      * row tpl.
111381      */
111382     getMetaRowTplFragments: function() {
111383         return {};
111384     },
111385
111386     getTableFragments: function() {
111387         return {};
111388     },
111389     
111390     /**
111391      * Provide additional data to the prepareData call within the grid view.
111392      * @param {Object} data The data for this particular record.
111393      * @param {Number} idx The row index for this record.
111394      * @param {Ext.data.Model} record The record instance
111395      * @param {Object} orig The original result from the prepareData call to massage.
111396      */
111397     getAdditionalData: function(data, idx, record, orig) {
111398         return {};
111399     },
111400     
111401     /**
111402      * Enable a feature
111403      */
111404     enable: function() {
111405         this.disabled = false;
111406     },
111407     
111408     /**
111409      * Disable a feature
111410      */
111411     disable: function() {
111412         this.disabled = true;
111413     }
111414     
111415 });
111416 /**
111417  * A small abstract class that contains the shared behaviour for any summary
111418  * calculations to be used in the grid.
111419  * @class Ext.grid.feature.AbstractSummary
111420  * @extends Ext.grid.feature.Feature
111421  * @ignore
111422  */
111423 Ext.define('Ext.grid.feature.AbstractSummary', {
111424     
111425     /* Begin Definitions */
111426    
111427     extend: 'Ext.grid.feature.Feature',
111428     
111429     alias: 'feature.abstractsummary',
111430    
111431     /* End Definitions */
111432    
111433    /**
111434     * @cfg {Boolean} showSummaryRow True to show the summary row. Defaults to <tt>true</tt>.
111435     */
111436     showSummaryRow: true,
111437     
111438     // @private
111439     nestedIdRe: /\{\{id\}([\w\-]*)\}/g,
111440     
111441     /**
111442      * Toggle whether or not to show the summary row.
111443      * @param {Boolan} visible True to show the summary row
111444      */
111445     toggleSummaryRow: function(visible){
111446         this.showSummaryRow = !!visible;
111447     },
111448     
111449     /**
111450      * Gets any fragments to be used in the tpl
111451      * @private
111452      * @return {Object} The fragments
111453      */
111454     getSummaryFragments: function(){
111455         var fragments = {};
111456         if (this.showSummaryRow) {
111457             Ext.apply(fragments, {
111458                 printSummaryRow: Ext.bind(this.printSummaryRow, this)
111459             });
111460         }
111461         return fragments;
111462     },
111463     
111464     /**
111465      * Prints a summary row
111466      * @private
111467      * @param {Object} index The index in the template
111468      * @return {String} The value of the summary row
111469      */
111470     printSummaryRow: function(index){
111471         var inner = this.view.getTableChunker().metaRowTpl.join('');
111472         
111473         inner = inner.replace('x-grid-row', 'x-grid-row-summary');
111474         inner = inner.replace('{{id}}', '{gridSummaryValue}');
111475         inner = inner.replace(this.nestedIdRe, '{id$1}');  
111476         inner = inner.replace('{[this.embedRowCls()]}', '{rowCls}');
111477         inner = inner.replace('{[this.embedRowAttr()]}', '{rowAttr}');
111478         inner = Ext.create('Ext.XTemplate', inner, {
111479             firstOrLastCls: Ext.view.TableChunker.firstOrLastCls
111480         });
111481         
111482         return inner.applyTemplate({
111483             columns: this.getPrintData(index)
111484         });
111485     },
111486     
111487     /**
111488      * Gets the value for the column from the attached data.
111489      * @param {Ext.grid.column.Column} column The header
111490      * @param {Object} data The current data
111491      * @return {String} The value to be rendered
111492      */
111493     getColumnValue: function(column, data){
111494         var comp = Ext.getCmp(column.id),
111495             value = data[column.dataIndex],
111496             renderer = comp.summaryRenderer || comp.renderer;
111497             
111498         if (renderer) {
111499             value = renderer.call(comp.scope || this, value, data, column.dataIndex);
111500         }
111501         return value;
111502     },
111503     
111504     /**
111505      * Get the summary data for a field.
111506      * @private
111507      * @param {Ext.data.Store} store The store to get the data from
111508      * @param {String/Function} type The type of aggregation. If a function is specified it will
111509      * be passed to the stores aggregate function.
111510      * @param {String} field The field to aggregate on
111511      * @param {Boolean} group True to aggregate in grouped mode 
111512      * @return {Mixed} See the return type for the store functions.
111513      */
111514     getSummary: function(store, type, field, group){
111515         if (type) {
111516             if (Ext.isFunction(type)) {
111517                 return store.aggregate(type, null, group);
111518             }
111519             
111520             switch (type) {
111521                 case 'count':
111522                     return store.count(group);
111523                 case 'min':
111524                     return store.min(field, group);
111525                 case 'max':
111526                     return store.max(field, group);
111527                 case 'sum':
111528                     return store.sum(field, group);
111529                 case 'average':
111530                     return store.average(field, group);
111531                 default:
111532                     return group ? {} : '';
111533                     
111534             }
111535         }
111536     }
111537     
111538 });
111539
111540 /**
111541  * @class Ext.grid.feature.Chunking
111542  * @extends Ext.grid.feature.Feature
111543  */
111544 Ext.define('Ext.grid.feature.Chunking', {
111545     extend: 'Ext.grid.feature.Feature',
111546     alias: 'feature.chunking',
111547     
111548     chunkSize: 20,
111549     rowHeight: Ext.isIE ? 27 : 26,
111550     visibleChunk: 0,
111551     hasFeatureEvent: false,
111552     attachEvents: function() {
111553         var grid = this.view.up('gridpanel'),
111554             scroller = grid.down('gridscroller[dock=right]');
111555         scroller.el.on('scroll', this.onBodyScroll, this, {buffer: 300});
111556         //this.view.on('bodyscroll', this.onBodyScroll, this, {buffer: 300});
111557     },
111558     
111559     onBodyScroll: function(e, t) {
111560         var view = this.view,
111561             top  = t.scrollTop,
111562             nextChunk = Math.floor(top / this.rowHeight / this.chunkSize);
111563         if (nextChunk !== this.visibleChunk) {
111564         
111565             this.visibleChunk = nextChunk;
111566             view.refresh();
111567             view.el.dom.scrollTop = top;
111568             //BrowserBug: IE6,7,8 quirks mode takes setting scrollTop 2x.
111569             view.el.dom.scrollTop = top;
111570         }
111571     },
111572     
111573     collectData: function(records, preppedRecords, startIndex, fullWidth, orig) {
111574         var o = {
111575             fullWidth: orig.fullWidth,
111576             chunks: []
111577         },
111578         //headerCt = this.view.headerCt,
111579         //colums = headerCt.getColumnsForTpl(),
111580         recordCount = orig.rows.length,
111581         start = 0,
111582         i = 0,
111583         visibleChunk = this.visibleChunk,
111584         chunk,
111585         rows,
111586         chunkLength;
111587
111588         for (; start < recordCount; start+=this.chunkSize, i++) {
111589             if (start+this.chunkSize > recordCount) {
111590                 chunkLength = recordCount - start;
111591             } else {
111592                 chunkLength = this.chunkSize;
111593             }
111594             
111595             if (i >= visibleChunk - 1 && i <= visibleChunk + 1) {
111596                 rows = orig.rows.slice(start, start+this.chunkSize);
111597             } else {
111598                 rows = [];
111599             }
111600             o.chunks.push({
111601                 rows: rows,
111602                 fullWidth: fullWidth,
111603                 chunkHeight: chunkLength * this.rowHeight
111604             });
111605         }
111606         
111607         
111608         return o;
111609     },
111610     
111611     getTableFragments: function() {
111612         return {
111613             openTableWrap: function() {
111614                 return '<tpl for="chunks"><div class="' + Ext.baseCSSPrefix + 'grid-chunk" style="height: {chunkHeight}px;">';
111615             },
111616             closeTableWrap: function() {
111617                 return '</div></tpl>';
111618             }
111619         };
111620     }
111621 });
111622
111623 /**
111624  * @class Ext.grid.feature.Grouping
111625  * @extends Ext.grid.feature.Feature
111626  * 
111627  * This feature allows to display the grid rows aggregated into groups as specified by the {@link Ext.data.Store#groupers}
111628  * specified on the Store. The group will show the title for the group name and then the appropriate records for the group
111629  * underneath. The groups can also be expanded and collapsed.
111630  * 
111631  * ## Extra Events
111632  * This feature adds several extra events that will be fired on the grid to interact with the groups:
111633  *
111634  *  - {@link #groupclick}
111635  *  - {@link #groupdblclick}
111636  *  - {@link #groupcontextmenu}
111637  *  - {@link #groupexpand}
111638  *  - {@link #groupcollapse}
111639  * 
111640  * ## Menu Augmentation
111641  * This feature adds extra options to the grid column menu to provide the user with functionality to modify the grouping.
111642  * This can be disabled by setting the {@link #enableGroupingMenu} option. The option to disallow grouping from being turned off
111643  * by thew user is {@link #enableNoGroups}.
111644  * 
111645  * ## Controlling Group Text
111646  * The {@link #groupHeaderTpl} is used to control the rendered title for each group. It can modified to customized
111647  * the default display.
111648  * 
111649  * ## Example Usage
111650  * 
111651  *     var groupingFeature = Ext.create('Ext.grid.feature.Grouping', {
111652  *         groupHeaderTpl: 'Group: {name} ({rows.length})', //print the number of items in the group
111653  *         startCollapsed: true // start all groups collapsed
111654  *     });
111655  * 
111656  * @ftype grouping
111657  * @author Nicolas Ferrero
111658  */
111659 Ext.define('Ext.grid.feature.Grouping', {
111660     extend: 'Ext.grid.feature.Feature',
111661     alias: 'feature.grouping',
111662
111663     eventPrefix: 'group',
111664     eventSelector: '.' + Ext.baseCSSPrefix + 'grid-group-hd',
111665
111666     constructor: function() {
111667         this.collapsedState = {};
111668         this.callParent(arguments);
111669     },
111670     
111671     /**
111672      * @event groupclick
111673      * @param {Ext.view.Table} view
111674      * @param {HTMLElement} node
111675      * @param {Number} unused
111676      * @param {Number} unused
111677      * @param {Ext.EventObject} e
111678      */
111679
111680     /**
111681      * @event groupdblclick
111682      * @param {Ext.view.Table} view
111683      * @param {HTMLElement} node
111684      * @param {Number} unused
111685      * @param {Number} unused
111686      * @param {Ext.EventObject} e
111687      */
111688
111689     /**
111690      * @event groupcontextmenu
111691      * @param {Ext.view.Table} view
111692      * @param {HTMLElement} node
111693      * @param {Number} unused
111694      * @param {Number} unused
111695      * @param {Ext.EventObject} e
111696      */
111697
111698     /**
111699      * @event groupcollapse
111700      * @param {Ext.view.Table} view
111701      * @param {HTMLElement} node
111702      * @param {Number} unused
111703      * @param {Number} unused
111704      * @param {Ext.EventObject} e
111705      */
111706
111707     /**
111708      * @event groupexpand
111709      * @param {Ext.view.Table} view
111710      * @param {HTMLElement} node
111711      * @param {Number} unused
111712      * @param {Number} unused
111713      * @param {Ext.EventObject} e
111714      */
111715
111716     /**
111717      * @cfg {String} groupHeaderTpl
111718      * Template snippet, this cannot be an actual template. {name} will be replaced with the current group.
111719      * Defaults to 'Group: {name}'
111720      */
111721     groupHeaderTpl: 'Group: {name}',
111722
111723     /**
111724      * @cfg {Number} depthToIndent
111725      * Number of pixels to indent per grouping level
111726      */
111727     depthToIndent: 17,
111728
111729     collapsedCls: Ext.baseCSSPrefix + 'grid-group-collapsed',
111730     hdCollapsedCls: Ext.baseCSSPrefix + 'grid-group-hd-collapsed',
111731
111732     /**
111733      * @cfg {String} groupByText Text displayed in the grid header menu for grouping by header
111734      * (defaults to 'Group By This Field').
111735      */
111736     groupByText : 'Group By This Field',
111737     /**
111738      * @cfg {String} showGroupsText Text displayed in the grid header for enabling/disabling grouping
111739      * (defaults to 'Show in Groups').
111740      */
111741     showGroupsText : 'Show in Groups',
111742
111743     /**
111744      * @cfg {Boolean} hideGroupedHeader<tt>true</tt> to hide the header that is currently grouped (defaults to <tt>false</tt>)
111745      */
111746     hideGroupedHeader : false,
111747
111748     /**
111749      * @cfg {Boolean} startCollapsed <tt>true</tt> to start all groups collapsed (defaults to <tt>false</tt>)
111750      */
111751     startCollapsed : false,
111752
111753     /**
111754      * @cfg {Boolean} enableGroupingMenu <tt>true</tt> to enable the grouping control in the header menu (defaults to <tt>true</tt>)
111755      */
111756     enableGroupingMenu : true,
111757
111758     /**
111759      * @cfg {Boolean} enableNoGroups <tt>true</tt> to allow the user to turn off grouping (defaults to <tt>true</tt>)
111760      */
111761     enableNoGroups : true,
111762     
111763     enable: function() {
111764         var me    = this,
111765             view  = me.view,
111766             store = view.store,
111767             groupToggleMenuItem;
111768             
111769         if (me.lastGroupIndex) {
111770             store.group(me.lastGroupIndex);
111771         }
111772         me.callParent();
111773         groupToggleMenuItem = me.view.headerCt.getMenu().down('#groupToggleMenuItem');
111774         groupToggleMenuItem.setChecked(true, true);
111775         view.refresh();
111776     },
111777
111778     disable: function() {
111779         var me    = this,
111780             view  = me.view,
111781             store = view.store,
111782             groupToggleMenuItem,
111783             lastGroup;
111784             
111785         lastGroup = store.groupers.first();
111786         if (lastGroup) {
111787             me.lastGroupIndex = lastGroup.property;
111788             store.groupers.clear();
111789         }
111790         
111791         me.callParent();
111792         groupToggleMenuItem = me.view.headerCt.getMenu().down('#groupToggleMenuItem');
111793         groupToggleMenuItem.setChecked(true, true);
111794         groupToggleMenuItem.setChecked(false, true);
111795         view.refresh();
111796     },
111797
111798     getFeatureTpl: function(values, parent, x, xcount) {
111799         var me = this;
111800         
111801         return [
111802             '<tpl if="typeof rows !== \'undefined\'">',
111803                 // group row tpl
111804                 '<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>',
111805                 // this is the rowbody
111806                 '<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>',
111807             '</tpl>'
111808         ].join('');
111809     },
111810
111811     getFragmentTpl: function() {
111812         return {
111813             indentByDepth: this.indentByDepth,
111814             depthToIndent: this.depthToIndent
111815         };
111816     },
111817
111818     indentByDepth: function(values) {
111819         var depth = values.depth || 0;
111820         return 'style="padding-left:'+ depth * this.depthToIndent + 'px;"';
111821     },
111822
111823     // Containers holding these components are responsible for
111824     // destroying them, we are just deleting references.
111825     destroy: function() {
111826         var me = this;
111827         
111828         delete me.view;
111829         delete me.prunedHeader;
111830     },
111831
111832     // perhaps rename to afterViewRender
111833     attachEvents: function() {
111834         var me = this,
111835             view = me.view,
111836             header, headerId, menu, menuItem;
111837
111838         view.on({
111839             scope: me,
111840             groupclick: me.onGroupClick,
111841             rowfocus: me.onRowFocus
111842         });
111843         view.store.on('groupchange', me.onGroupChange, me);
111844
111845         me.pruneGroupedHeader();
111846
111847         if (me.enableGroupingMenu) {
111848             me.injectGroupingMenu();
111849         }
111850
111851         if (me.hideGroupedHeader) {
111852             header = view.headerCt.down('gridcolumn[dataIndex=' + me.getGroupField() + ']');
111853             headerId = header.id;
111854             menu = view.headerCt.getMenu();
111855             menuItem = menu.down('menuitem[headerId='+ headerId +']');
111856             if (menuItem) {
111857                 menuItem.setChecked(false);
111858             }
111859         }
111860     },
111861     
111862     injectGroupingMenu: function() {
111863         var me       = this,
111864             view     = me.view,
111865             headerCt = view.headerCt;
111866         headerCt.showMenuBy = me.showMenuBy;
111867         headerCt.getMenuItems = me.getMenuItems();
111868     },
111869     
111870     showMenuBy: function(t, header) {
111871         var menu = this.getMenu(),
111872             groupMenuItem  = menu.down('#groupMenuItem'),
111873             groupableMth = header.groupable === false ?  'disable' : 'enable';
111874             
111875         groupMenuItem[groupableMth]();
111876         Ext.grid.header.Container.prototype.showMenuBy.apply(this, arguments);
111877     },
111878     
111879     getMenuItems: function() {
111880         var me                 = this,
111881             groupByText        = me.groupByText,
111882             disabled           = me.disabled,
111883             showGroupsText     = me.showGroupsText,
111884             enableNoGroups     = me.enableNoGroups,
111885             groupMenuItemClick = Ext.Function.bind(me.onGroupMenuItemClick, me),
111886             groupToggleMenuItemClick = Ext.Function.bind(me.onGroupToggleMenuItemClick, me)
111887         
111888         // runs in the scope of headerCt
111889         return function() {
111890             var o = Ext.grid.header.Container.prototype.getMenuItems.call(this);
111891             o.push('-', {
111892                 itemId: 'groupMenuItem',
111893                 text: groupByText,
111894                 handler: groupMenuItemClick
111895             });
111896             if (enableNoGroups) {
111897                 o.push({
111898                     itemId: 'groupToggleMenuItem',
111899                     text: showGroupsText,
111900                     checked: !disabled,
111901                     checkHandler: groupToggleMenuItemClick
111902                 });
111903             }
111904             return o;
111905         };
111906     },
111907
111908
111909     /**
111910      * Group by the header the user has clicked on.
111911      * @private
111912      */
111913     onGroupMenuItemClick: function(menuItem, e) {
111914         var menu = menuItem.parentMenu,
111915             hdr  = menu.activeHeader,
111916             view = this.view;
111917
111918         delete this.lastGroupIndex;
111919         this.enable();
111920         view.store.group(hdr.dataIndex);
111921         this.pruneGroupedHeader();
111922         
111923     },
111924
111925     /**
111926      * Turn on and off grouping via the menu
111927      * @private
111928      */
111929     onGroupToggleMenuItemClick: function(menuItem, checked) {
111930         this[checked ? 'enable' : 'disable']();
111931     },
111932
111933     /**
111934      * Prunes the grouped header from the header container
111935      * @private
111936      */
111937     pruneGroupedHeader: function() {
111938         var me         = this,
111939             view       = me.view,
111940             store      = view.store,
111941             groupField = me.getGroupField(),
111942             headerCt   = view.headerCt,
111943             header     = headerCt.down('header[dataIndex=' + groupField + ']');
111944
111945         if (header) {
111946             if (me.prunedHeader) {
111947                 me.prunedHeader.show();
111948             }
111949             me.prunedHeader = header;
111950             header.hide();
111951         }
111952     },
111953
111954     getGroupField: function(){
111955         var group = this.view.store.groupers.first();
111956         if (group) {
111957             return group.property;    
111958         }
111959         return ''; 
111960     },
111961
111962     /**
111963      * When a row gains focus, expand the groups above it
111964      * @private
111965      */
111966     onRowFocus: function(rowIdx) {
111967         var node    = this.view.getNode(rowIdx),
111968             groupBd = Ext.fly(node).up('.' + this.collapsedCls);
111969
111970         if (groupBd) {
111971             // for multiple level groups, should expand every groupBd
111972             // above
111973             this.expand(groupBd);
111974         }
111975     },
111976
111977     /**
111978      * Expand a group by the groupBody
111979      * @param {Ext.core.Element} groupBd
111980      * @private
111981      */
111982     expand: function(groupBd) {
111983         var me = this,
111984             view = me.view,
111985             grid = view.up('gridpanel'),
111986             groupBdDom = Ext.getDom(groupBd);
111987             
111988         me.collapsedState[groupBdDom.id] = false;
111989
111990         groupBd.removeCls(me.collapsedCls);
111991         groupBd.prev().removeCls(me.hdCollapsedCls);
111992
111993         grid.determineScrollbars();
111994         grid.invalidateScroller();
111995         view.fireEvent('groupexpand');
111996     },
111997
111998     /**
111999      * Collapse a group by the groupBody
112000      * @param {Ext.core.Element} groupBd
112001      * @private
112002      */
112003     collapse: function(groupBd) {
112004         var me = this,
112005             view = me.view,
112006             grid = view.up('gridpanel'),
112007             groupBdDom = Ext.getDom(groupBd);
112008             
112009         me.collapsedState[groupBdDom.id] = true;
112010
112011         groupBd.addCls(me.collapsedCls);
112012         groupBd.prev().addCls(me.hdCollapsedCls);
112013
112014         grid.determineScrollbars();
112015         grid.invalidateScroller();
112016         view.fireEvent('groupcollapse');
112017     },
112018     
112019     onGroupChange: function(){
112020         this.view.refresh();
112021     },
112022
112023     /**
112024      * Toggle between expanded/collapsed state when clicking on
112025      * the group.
112026      * @private
112027      */
112028     onGroupClick: function(view, group, idx, foo, e) {
112029         var me = this,
112030             toggleCls = me.toggleCls,
112031             groupBd = Ext.fly(group.nextSibling, '_grouping');
112032
112033         if (groupBd.hasCls(me.collapsedCls)) {
112034             me.expand(groupBd);
112035         } else {
112036             me.collapse(groupBd);
112037         }
112038     },
112039
112040     // Injects isRow and closeRow into the metaRowTpl.
112041     getMetaRowTplFragments: function() {
112042         return {
112043             isRow: this.isRow,
112044             closeRow: this.closeRow
112045         };
112046     },
112047
112048     // injected into rowtpl and wrapped around metaRowTpl
112049     // becomes part of the standard tpl
112050     isRow: function() {
112051         return '<tpl if="typeof rows === \'undefined\'">';
112052     },
112053
112054     // injected into rowtpl and wrapped around metaRowTpl
112055     // becomes part of the standard tpl
112056     closeRow: function() {
112057         return '</tpl>';
112058     },
112059
112060     // isRow and closeRow are injected via getMetaRowTplFragments
112061     mutateMetaRowTpl: function(metaRowTpl) {
112062         metaRowTpl.unshift('{[this.isRow()]}');
112063         metaRowTpl.push('{[this.closeRow()]}');
112064     },
112065
112066     // injects an additional style attribute via tdAttrKey with the proper
112067     // amount of padding
112068     getAdditionalData: function(data, idx, record, orig) {
112069         var view = this.view,
112070             hCt  = view.headerCt,
112071             col  = hCt.items.getAt(0),
112072             o = {},
112073             tdAttrKey = col.id + '-tdAttr';
112074
112075         // maintain the current tdAttr that a user may ahve set.
112076         o[tdAttrKey] = this.indentByDepth(data) + " " + (orig[tdAttrKey] ? orig[tdAttrKey] : '');
112077         o.collapsed = 'true';
112078         return o;
112079     },
112080
112081     // return matching preppedRecords
112082     getGroupRows: function(group, records, preppedRecords, fullWidth) {
112083         var me = this,
112084             children = group.children,
112085             rows = group.rows = [],
112086             view = me.view;
112087         group.viewId = view.id;
112088
112089         Ext.Array.each(records, function(record, idx) {
112090             if (Ext.Array.indexOf(children, record) != -1) {
112091                 rows.push(Ext.apply(preppedRecords[idx], {
112092                     depth: 1
112093                 }));
112094             }
112095         });
112096         delete group.children;
112097         group.fullWidth = fullWidth;
112098         if (me.collapsedState[view.id + '-gp-' + group.name]) {
112099             group.collapsedCls = me.collapsedCls;
112100             group.hdCollapsedCls = me.hdCollapsedCls;
112101         }
112102
112103         return group;
112104     },
112105
112106     // return the data in a grouped format.
112107     collectData: function(records, preppedRecords, startIndex, fullWidth, o) {
112108         var me    = this,
112109             store = me.view.store,
112110             groups;
112111             
112112         if (!me.disabled && store.isGrouped()) {
112113             groups = store.getGroups();
112114             Ext.Array.each(groups, function(group, idx){
112115                 me.getGroupRows(group, records, preppedRecords, fullWidth);
112116             }, me);
112117             return {
112118                 rows: groups,
112119                 fullWidth: fullWidth
112120             };
112121         }
112122         return o;
112123     },
112124     
112125     // adds the groupName to the groupclick, groupdblclick, groupcontextmenu
112126     // events that are fired on the view. Chose not to return the actual
112127     // group itself because of its expense and because developers can simply
112128     // grab the group via store.getGroups(groupName)
112129     getFireEventArgs: function(type, view, featureTarget) {
112130         var returnArray = [type, view, featureTarget],
112131             groupBd     = Ext.fly(featureTarget.nextSibling, '_grouping'),
112132             groupBdId   = Ext.getDom(groupBd).id,
112133             prefix      = view.id + '-gp-',
112134             groupName   = groupBdId.substr(prefix.length);
112135         
112136         returnArray.push(groupName);
112137         
112138         return returnArray;
112139     }
112140 });
112141
112142 /**
112143  * @class Ext.grid.feature.GroupingSummary
112144  * @extends Ext.grid.feature.Grouping
112145  * 
112146  * This feature adds an aggregate summary row at the bottom of each group that is provided
112147  * by the {@link Ext.grid.feature.Grouping} feature. There are 2 aspects to the summary:
112148  * 
112149  * ## Calculation
112150  * 
112151  * The summary value needs to be calculated for each column in the grid. This is controlled
112152  * by the summaryType option specified on the column. There are several built in summary types,
112153  * which can be specified as a string on the column configuration. These call underlying methods
112154  * on the store:
112155  *
112156  *  - {@link Ext.data.Store#count count}
112157  *  - {@link Ext.data.Store#sum sum}
112158  *  - {@link Ext.data.Store#min min}
112159  *  - {@link Ext.data.Store#max max}
112160  *  - {@link Ext.data.Store#average average}
112161  *
112162  * Alternatively, the summaryType can be a function definition. If this is the case,
112163  * the function is called with an array of records to calculate the summary value.
112164  * 
112165  * ## Rendering
112166  * 
112167  * Similar to a column, the summary also supports a summaryRenderer function. This
112168  * summaryRenderer is called before displaying a value. The function is optional, if
112169  * not specified the default calculated value is shown. The summaryRenderer is called with:
112170  *
112171  *  - value {Object} - The calculated value.
112172  *  - data {Object} - Contains all raw summary values for the row.
112173  *  - field {String} - The name of the field we are calculating
112174  * 
112175  * ## Example Usage
112176  *
112177  *     Ext.define('TestResult', {
112178  *         extend: 'Ext.data.Model',
112179  *         fields: ['student', 'subject', {
112180  *             name: 'mark',
112181  *             type: 'int'
112182  *         }]
112183  *     });
112184  *     
112185  *     Ext.create('Ext.grid.Panel', {
112186  *         width: 200,
112187  *         height: 240,
112188  *         renderTo: document.body,
112189  *         features: [{
112190  *             groupHeaderTpl: 'Subject: {name}',
112191  *             ftype: 'groupingsummary'
112192  *         }],
112193  *         store: {
112194  *             model: 'TestResult',
112195  *             groupField: 'subject',
112196  *             data: [{
112197  *                 student: 'Student 1',
112198  *                 subject: 'Math',
112199  *                 mark: 84
112200  *             },{
112201  *                 student: 'Student 1',
112202  *                 subject: 'Science',
112203  *                 mark: 72
112204  *             },{
112205  *                 student: 'Student 2',
112206  *                 subject: 'Math',
112207  *                 mark: 96
112208  *             },{
112209  *                 student: 'Student 2',
112210  *                 subject: 'Science',
112211  *                 mark: 68
112212  *             }]
112213  *         },
112214  *         columns: [{
112215  *             dataIndex: 'student',
112216  *             text: 'Name',
112217  *             summaryType: 'count',
112218  *             summaryRenderer: function(value){
112219  *                 return Ext.String.format('{0} student{1}', value, value !== 1 ? 's' : ''); 
112220  *             }
112221  *         }, {
112222  *             dataIndex: 'mark',
112223  *             text: 'Mark',
112224  *             summaryType: 'average'
112225  *         }]
112226  *     });
112227  */
112228 Ext.define('Ext.grid.feature.GroupingSummary', {
112229     
112230     /* Begin Definitions */
112231     
112232     extend: 'Ext.grid.feature.Grouping',
112233     
112234     alias: 'feature.groupingsummary',
112235     
112236     mixins: {
112237         summary: 'Ext.grid.feature.AbstractSummary'
112238     },
112239     
112240     /* End Definitions */
112241
112242      
112243    /**
112244     * Modifies the row template to include the summary row.
112245     * @private
112246     * @return {String} The modified template
112247     */
112248    getFeatureTpl: function() {
112249         var tpl = this.callParent(arguments);
112250             
112251         if (this.showSummaryRow) {
112252             // lop off the end </tpl> so we can attach it
112253             tpl = tpl.replace('</tpl>', '');
112254             tpl += '{[this.printSummaryRow(xindex)]}</tpl>';
112255         }
112256         return tpl;
112257     },
112258     
112259     /**
112260      * Gets any fragments needed for the template.
112261      * @private
112262      * @return {Object} The fragments
112263      */
112264     getFragmentTpl: function() {
112265         var me = this,
112266             fragments = me.callParent();
112267             
112268         Ext.apply(fragments, me.getSummaryFragments());
112269         if (me.showSummaryRow) {
112270             // this gets called before render, so we'll setup the data here.
112271             me.summaryGroups = me.view.store.getGroups();
112272             me.summaryData = me.generateSummaryData();
112273         }
112274         return fragments;
112275     },
112276     
112277     /**
112278      * Gets the data for printing a template row
112279      * @private
112280      * @param {Number} index The index in the template
112281      * @return {Array} The template values
112282      */
112283     getPrintData: function(index){
112284         var me = this,
112285             columns = me.view.headerCt.getColumnsForTpl(),
112286             i = 0,
112287             length = columns.length,
112288             data = [],
112289             name = me.summaryGroups[index - 1].name,
112290             active = me.summaryData[name],
112291             column;
112292             
112293         for (; i < length; ++i) {
112294             column = columns[i];
112295             column.gridSummaryValue = this.getColumnValue(column, active);
112296             data.push(column);
112297         }
112298         return data;
112299     },
112300     
112301     /**
112302      * Generates all of the summary data to be used when processing the template
112303      * @private
112304      * @return {Object} The summary data
112305      */
112306     generateSummaryData: function(){
112307         var me = this,
112308             data = {},
112309             remoteData = {},
112310             store = me.view.store,
112311             groupField = this.getGroupField(),
112312             reader = store.proxy.reader,
112313             groups = me.summaryGroups,
112314             columns = me.view.headerCt.getColumnsForTpl(),
112315             i,
112316             length,
112317             fieldData,
112318             root,
112319             key,
112320             comp;
112321             
112322         for (i = 0, length = groups.length; i < length; ++i) {
112323             data[groups[i].name] = {};
112324         }
112325         
112326     /**
112327      * @cfg {String} remoteRoot.  The name of the property
112328      * which contains the Array of summary objects.  Defaults to <tt>undefined</tt>.
112329      * It allows to use server-side calculated summaries.
112330      */
112331         if (me.remoteRoot && reader.rawData) {
112332             // reset reader root and rebuild extractors to extract summaries data
112333             root = reader.root;
112334             reader.root = me.remoteRoot;
112335             reader.buildExtractors(true);
112336             Ext.Array.each(reader.getRoot(reader.rawData), function(value) {
112337                  data[value[groupField]] = value;
112338                  data[value[groupField]]._remote = true;
112339             });
112340             // restore initial reader configuration
112341             reader.root = root;
112342             reader.buildExtractors(true);
112343         }
112344         
112345         for (i = 0, length = columns.length; i < length; ++i) {
112346             comp = Ext.getCmp(columns[i].id);
112347             fieldData = me.getSummary(store, comp.summaryType, comp.dataIndex, true);
112348             
112349             for (key in fieldData) {
112350                 if (fieldData.hasOwnProperty(key)) {
112351                     if (!data[key]._remote) {
112352                         data[key][comp.dataIndex] = fieldData[key];
112353                     }
112354                 }
112355             }
112356         }
112357         return data;
112358     }
112359 });
112360
112361 /**
112362  * @class Ext.grid.feature.RowBody
112363  * @extends Ext.grid.feature.Feature
112364  *
112365  * The rowbody feature enhances the grid's markup to have an additional
112366  * tr -> td -> div which spans the entire width of the original row.
112367  *
112368  * This is useful to to associate additional information with a particular
112369  * record in a grid.
112370  *
112371  * Rowbodies are initially hidden unless you override getAdditionalData.
112372  *
112373  * Will expose additional events on the gridview with the prefix of 'rowbody'.
112374  * For example: 'rowbodyclick', 'rowbodydblclick', 'rowbodycontextmenu'.
112375  *
112376  * @ftype rowbody
112377  */
112378 Ext.define('Ext.grid.feature.RowBody', {
112379     extend: 'Ext.grid.feature.Feature',
112380     alias: 'feature.rowbody',
112381     rowBodyHiddenCls: Ext.baseCSSPrefix + 'grid-row-body-hidden',
112382     rowBodyTrCls: Ext.baseCSSPrefix + 'grid-rowbody-tr',
112383     rowBodyTdCls: Ext.baseCSSPrefix + 'grid-cell-rowbody',
112384     rowBodyDivCls: Ext.baseCSSPrefix + 'grid-rowbody',
112385
112386     eventPrefix: 'rowbody',
112387     eventSelector: '.' + Ext.baseCSSPrefix + 'grid-rowbody-tr',
112388     
112389     getRowBody: function(values) {
112390         return [
112391             '<tr class="' + this.rowBodyTrCls + ' {rowBodyCls}">',
112392                 '<td class="' + this.rowBodyTdCls + '" colspan="{rowBodyColspan}">',
112393                     '<div class="' + this.rowBodyDivCls + '">{rowBody}</div>',
112394                 '</td>',
112395             '</tr>'
112396         ].join('');
112397     },
112398     
112399     // injects getRowBody into the metaRowTpl.
112400     getMetaRowTplFragments: function() {
112401         return {
112402             getRowBody: this.getRowBody,
112403             rowBodyTrCls: this.rowBodyTrCls,
112404             rowBodyTdCls: this.rowBodyTdCls,
112405             rowBodyDivCls: this.rowBodyDivCls
112406         };
112407     },
112408
112409     mutateMetaRowTpl: function(metaRowTpl) {
112410         metaRowTpl.push('{[this.getRowBody(values)]}');
112411     },
112412
112413     /**
112414      * Provide additional data to the prepareData call within the grid view.
112415      * The rowbody feature adds 3 additional variables into the grid view's template.
112416      * These are rowBodyCls, rowBodyColspan, and rowBody.
112417      * @param {Object} data The data for this particular record.
112418      * @param {Number} idx The row index for this record.
112419      * @param {Ext.data.Model} record The record instance
112420      * @param {Object} orig The original result from the prepareData call to massage.
112421      */
112422     getAdditionalData: function(data, idx, record, orig) {
112423         var headerCt = this.view.headerCt,
112424             colspan  = headerCt.getColumnCount();
112425
112426         return {
112427             rowBody: "",
112428             rowBodyCls: this.rowBodyCls,
112429             rowBodyColspan: colspan
112430         };
112431     }
112432 });
112433 /**
112434  * @class Ext.grid.feature.RowWrap
112435  * @extends Ext.grid.feature.Feature
112436  * @private
112437  */
112438 Ext.define('Ext.grid.feature.RowWrap', {
112439     extend: 'Ext.grid.feature.Feature',
112440     alias: 'feature.rowwrap',
112441
112442     // turn off feature events.
112443     hasFeatureEvent: false,
112444     
112445     mutateMetaRowTpl: function(metaRowTpl) {        
112446         // Remove "x-grid-row" from the first row, note this could be wrong
112447         // if some other feature unshifted things in front.
112448         metaRowTpl[0] = metaRowTpl[0].replace(Ext.baseCSSPrefix + 'grid-row', '');
112449         metaRowTpl[0] = metaRowTpl[0].replace("{[this.embedRowCls()]}", "");
112450         // 2
112451         metaRowTpl.unshift('<table class="' + Ext.baseCSSPrefix + 'grid-table ' + Ext.baseCSSPrefix + 'grid-table-resizer" style="width: {[this.embedFullWidth()]}px;">');
112452         // 1
112453         metaRowTpl.unshift('<tr class="' + Ext.baseCSSPrefix + 'grid-row {[this.embedRowCls()]}"><td colspan="{[this.embedColSpan()]}"><div class="' + Ext.baseCSSPrefix + 'grid-rowwrap-div">');
112454         
112455         // 3
112456         metaRowTpl.push('</table>');
112457         // 4
112458         metaRowTpl.push('</div></td></tr>');
112459     },
112460     
112461     embedColSpan: function() {
112462         return '{colspan}';
112463     },
112464     
112465     embedFullWidth: function() {
112466         return '{fullWidth}';
112467     },
112468     
112469     getAdditionalData: function(data, idx, record, orig) {
112470         var headerCt = this.view.headerCt,
112471             colspan  = headerCt.getColumnCount(),
112472             fullWidth = headerCt.getFullWidth(),
112473             items    = headerCt.query('gridcolumn'),
112474             itemsLn  = items.length,
112475             i = 0,
112476             o = {
112477                 colspan: colspan,
112478                 fullWidth: fullWidth
112479             },
112480             id,
112481             tdClsKey,
112482             colResizerCls;
112483
112484         for (; i < itemsLn; i++) {
112485             id = items[i].id;
112486             tdClsKey = id + '-tdCls';
112487             colResizerCls = Ext.baseCSSPrefix + 'grid-col-resizer-'+id;
112488             // give the inner td's the resizer class
112489             // while maintaining anything a user may have injected via a custom
112490             // renderer
112491             o[tdClsKey] = colResizerCls + " " + (orig[tdClsKey] ? orig[tdClsKey] : '');
112492             // TODO: Unhackify the initial rendering width's
112493             o[id+'-tdAttr'] = " style=\"width: " + (items[i].hidden ? 0 : items[i].getDesiredWidth()) + "px;\" "/* + (i === 0 ? " rowspan=\"2\"" : "")*/;
112494             if (orig[id+'-tdAttr']) {
112495                 o[id+'-tdAttr'] += orig[id+'-tdAttr'];
112496             }
112497             
112498         }
112499
112500         return o;
112501     },
112502     
112503     getMetaRowTplFragments: function() {
112504         return {
112505             embedFullWidth: this.embedFullWidth,
112506             embedColSpan: this.embedColSpan
112507         };
112508     }
112509     
112510 });
112511 /**
112512  * @class Ext.grid.feature.Summary
112513  * @extends Ext.grid.feature.AbstractSummary
112514  * 
112515  * This feature is used to place a summary row at the bottom of the grid. If using a grouping, 
112516  * see {@link Ext.grid.feature.GroupingSummary}. There are 2 aspects to calculating the summaries, 
112517  * calculation and rendering.
112518  * 
112519  * ## Calculation
112520  * The summary value needs to be calculated for each column in the grid. This is controlled
112521  * by the summaryType option specified on the column. There are several built in summary types,
112522  * which can be specified as a string on the column configuration. These call underlying methods
112523  * on the store:
112524  *
112525  *  - {@link Ext.data.Store#count count}
112526  *  - {@link Ext.data.Store#sum sum}
112527  *  - {@link Ext.data.Store#min min}
112528  *  - {@link Ext.data.Store#max max}
112529  *  - {@link Ext.data.Store#average average}
112530  *
112531  * Alternatively, the summaryType can be a function definition. If this is the case,
112532  * the function is called with an array of records to calculate the summary value.
112533  * 
112534  * ## Rendering
112535  * Similar to a column, the summary also supports a summaryRenderer function. This
112536  * summaryRenderer is called before displaying a value. The function is optional, if
112537  * not specified the default calculated value is shown. The summaryRenderer is called with:
112538  *
112539  *  - value {Object} - The calculated value.
112540  *  - data {Object} - Contains all raw summary values for the row.
112541  *  - field {String} - The name of the field we are calculating
112542  * 
112543  * ## Example Usage
112544  *
112545  *     Ext.define('TestResult', {
112546  *         extend: 'Ext.data.Model',
112547  *         fields: ['student', {
112548  *             name: 'mark',
112549  *             type: 'int'
112550  *         }]
112551  *     });
112552  *     
112553  *     Ext.create('Ext.grid.Panel', {
112554  *         width: 200,
112555  *         height: 140,
112556  *         renderTo: document.body,
112557  *         features: [{
112558  *             ftype: 'summary'
112559  *         }],
112560  *         store: {
112561  *             model: 'TestResult',
112562  *             data: [{
112563  *                 student: 'Student 1',
112564  *                 mark: 84
112565  *             },{
112566  *                 student: 'Student 2',
112567  *                 mark: 72
112568  *             },{
112569  *                 student: 'Student 3',
112570  *                 mark: 96
112571  *             },{
112572  *                 student: 'Student 4',
112573  *                 mark: 68
112574  *             }]
112575  *         },
112576  *         columns: [{
112577  *             dataIndex: 'student',
112578  *             text: 'Name',
112579  *             summaryType: 'count',
112580  *             summaryRenderer: function(value){
112581  *                 return Ext.String.format('{0} student{1}', value, value !== 1 ? 's' : ''); 
112582  *             }
112583  *         }, {
112584  *             dataIndex: 'mark',
112585  *             text: 'Mark',
112586  *             summaryType: 'average'
112587  *         }]
112588  *     });
112589  */
112590 Ext.define('Ext.grid.feature.Summary', {
112591     
112592     /* Begin Definitions */
112593     
112594     extend: 'Ext.grid.feature.AbstractSummary',
112595     
112596     alias: 'feature.summary',
112597     
112598     /* End Definitions */
112599     
112600     /**
112601      * Gets any fragments needed for the template.
112602      * @private
112603      * @return {Object} The fragments
112604      */
112605     getFragmentTpl: function() {
112606         // this gets called before render, so we'll setup the data here.
112607         this.summaryData = this.generateSummaryData(); 
112608         return this.getSummaryFragments();
112609     },
112610     
112611     /**
112612      * Overrides the closeRows method on the template so we can include our own custom
112613      * footer.
112614      * @private
112615      * @return {Object} The custom fragments
112616      */
112617     getTableFragments: function(){
112618         if (this.showSummaryRow) {
112619             return {
112620                 closeRows: this.closeRows
112621             };
112622         }
112623     },
112624     
112625     /**
112626      * Provide our own custom footer for the grid.
112627      * @private
112628      * @return {String} The custom footer
112629      */
112630     closeRows: function() {
112631         return '</tpl>{[this.printSummaryRow()]}';
112632     },
112633     
112634     /**
112635      * Gets the data for printing a template row
112636      * @private
112637      * @param {Number} index The index in the template
112638      * @return {Array} The template values
112639      */
112640     getPrintData: function(index){
112641         var me = this,
112642             columns = me.view.headerCt.getColumnsForTpl(),
112643             i = 0,
112644             length = columns.length,
112645             data = [],
112646             active = me.summaryData,
112647             column;
112648             
112649         for (; i < length; ++i) {
112650             column = columns[i];
112651             column.gridSummaryValue = this.getColumnValue(column, active);
112652             data.push(column);
112653         }
112654         return data;
112655     },
112656     
112657     /**
112658      * Generates all of the summary data to be used when processing the template
112659      * @private
112660      * @return {Object} The summary data
112661      */
112662     generateSummaryData: function(){
112663         var me = this,
112664             data = {},
112665             store = me.view.store,
112666             columns = me.view.headerCt.getColumnsForTpl(),
112667             i = 0,
112668             length = columns.length,
112669             fieldData,
112670             key,
112671             comp;
112672             
112673         for (i = 0, length = columns.length; i < length; ++i) {
112674             comp = Ext.getCmp(columns[i].id);
112675             data[comp.dataIndex] = me.getSummary(store, comp.summaryType, comp.dataIndex, false);
112676         }
112677         return data;
112678     }
112679 });
112680 /**
112681  * @class Ext.grid.header.DragZone
112682  * @extends Ext.dd.DragZone
112683  * @private
112684  */
112685 Ext.define('Ext.grid.header.DragZone', {
112686     extend: 'Ext.dd.DragZone',
112687     colHeaderCls: Ext.baseCSSPrefix + 'column-header',
112688     maxProxyWidth: 120,
112689
112690     constructor: function(headerCt) {
112691         this.headerCt = headerCt;
112692         this.ddGroup =  this.getDDGroup();
112693         this.callParent([headerCt.el]);
112694         this.proxy.el.addCls(Ext.baseCSSPrefix + 'grid-col-dd');
112695     },
112696
112697     getDDGroup: function() {
112698         return 'header-dd-zone-' + this.headerCt.up('[scrollerOwner]').id;
112699     },
112700
112701     getDragData: function(e) {
112702         var header = e.getTarget('.'+this.colHeaderCls),
112703             headerCmp;
112704
112705         if (header) {
112706             headerCmp = Ext.getCmp(header.id);
112707             if (!this.headerCt.dragging && headerCmp.draggable && !(headerCmp.isOnLeftEdge(e) || headerCmp.isOnRightEdge(e))) {
112708                 var ddel = document.createElement('div');
112709                 ddel.innerHTML = Ext.getCmp(header.id).text;
112710                 return {
112711                     ddel: ddel,
112712                     header: headerCmp
112713                 };
112714             }
112715         }
112716         return false;
112717     },
112718
112719     onBeforeDrag: function() {
112720         return !(this.headerCt.dragging || this.disabled);
112721     },
112722
112723     onInitDrag: function() {
112724         this.headerCt.dragging = true;
112725         this.callParent(arguments);
112726     },
112727
112728     onDragDrop: function() {
112729         this.headerCt.dragging = false;
112730         this.callParent(arguments);
112731     },
112732
112733     afterRepair: function() {
112734         this.callParent();
112735         this.headerCt.dragging = false;
112736     },
112737
112738     getRepairXY: function() {
112739         return this.dragData.header.el.getXY();
112740     },
112741     
112742     disable: function() {
112743         this.disabled = true;
112744     },
112745     
112746     enable: function() {
112747         this.disabled = false;
112748     }
112749 });
112750
112751 /**
112752  * @class Ext.grid.header.DropZone
112753  * @extends Ext.dd.DropZone
112754  * @private
112755  */
112756 Ext.define('Ext.grid.header.DropZone', {
112757     extend: 'Ext.dd.DropZone',
112758     colHeaderCls: Ext.baseCSSPrefix + 'column-header',
112759     proxyOffsets: [-4, -9],
112760
112761     constructor: function(headerCt){
112762         this.headerCt = headerCt;
112763         this.ddGroup = this.getDDGroup();
112764         this.callParent([headerCt.el]);
112765     },
112766
112767     getDDGroup: function() {
112768         return 'header-dd-zone-' + this.headerCt.up('[scrollerOwner]').id;
112769     },
112770
112771     getTargetFromEvent : function(e){
112772         return e.getTarget('.' + this.colHeaderCls);
112773     },
112774
112775     getTopIndicator: function() {
112776         if (!this.topIndicator) {
112777             this.topIndicator = Ext.core.DomHelper.append(Ext.getBody(), {
112778                 cls: "col-move-top",
112779                 html: "&#160;"
112780             }, true);
112781         }
112782         return this.topIndicator;
112783     },
112784
112785     getBottomIndicator: function() {
112786         if (!this.bottomIndicator) {
112787             this.bottomIndicator = Ext.core.DomHelper.append(Ext.getBody(), {
112788                 cls: "col-move-bottom",
112789                 html: "&#160;"
112790             }, true);
112791         }
112792         return this.bottomIndicator;
112793     },
112794
112795     getLocation: function(e, t) {
112796         var x      = e.getXY()[0],
112797             region = Ext.fly(t).getRegion(),
112798             pos, header;
112799
112800         if ((region.right - x) <= (region.right - region.left) / 2) {
112801             pos = "after";
112802         } else {
112803             pos = "before";
112804         }
112805         return {
112806             pos: pos,
112807             header: Ext.getCmp(t.id),
112808             node: t
112809         };
112810     },
112811
112812     positionIndicator: function(draggedHeader, node, e){
112813         var location = this.getLocation(e, node),
112814             header = location.header,
112815             pos    = location.pos,
112816             nextHd = draggedHeader.nextSibling('gridcolumn:not([hidden])'),
112817             prevHd = draggedHeader.previousSibling('gridcolumn:not([hidden])'),
112818             region, topIndicator, bottomIndicator, topAnchor, bottomAnchor,
112819             topXY, bottomXY, headerCtEl, minX, maxX;
112820
112821         // Cannot drag beyond non-draggable start column
112822         if (!header.draggable && header.getIndex() == 0) {
112823             return false;
112824         }
112825
112826         this.lastLocation = location;
112827
112828         if ((draggedHeader !== header) &&
112829             ((pos === "before" && nextHd !== header) ||
112830             (pos === "after" && prevHd !== header)) &&
112831             !header.isDescendantOf(draggedHeader)) {
112832
112833             // As we move in between different DropZones that are in the same
112834             // group (such as the case when in a locked grid), invalidateDrop
112835             // on the other dropZones.
112836             var allDropZones = Ext.dd.DragDropManager.getRelated(this),
112837                 ln = allDropZones.length,
112838                 i  = 0,
112839                 dropZone;
112840
112841             for (; i < ln; i++) {
112842                 dropZone = allDropZones[i];
112843                 if (dropZone !== this && dropZone.invalidateDrop) {
112844                     dropZone.invalidateDrop();
112845                 }
112846             }
112847
112848
112849             this.valid = true;
112850             topIndicator = this.getTopIndicator();
112851             bottomIndicator = this.getBottomIndicator();
112852             if (pos === 'before') {
112853                 topAnchor = 'tl';
112854                 bottomAnchor = 'bl';
112855             } else {
112856                 topAnchor = 'tr';
112857                 bottomAnchor = 'br';
112858             }
112859             topXY = header.el.getAnchorXY(topAnchor);
112860             bottomXY = header.el.getAnchorXY(bottomAnchor);
112861
112862             // constrain the indicators to the viewable section
112863             headerCtEl = this.headerCt.el;
112864             minX = headerCtEl.getLeft();
112865             maxX = headerCtEl.getRight();
112866
112867             topXY[0] = Ext.Number.constrain(topXY[0], minX, maxX);
112868             bottomXY[0] = Ext.Number.constrain(bottomXY[0], minX, maxX);
112869
112870             // adjust by offsets, this is to center the arrows so that they point
112871             // at the split point
112872             topXY[0] -= 4;
112873             topXY[1] -= 9;
112874             bottomXY[0] -= 4;
112875
112876             // position and show indicators
112877             topIndicator.setXY(topXY);
112878             bottomIndicator.setXY(bottomXY);
112879             topIndicator.show();
112880             bottomIndicator.show();
112881         // invalidate drop operation and hide indicators
112882         } else {
112883             this.invalidateDrop();
112884         }
112885     },
112886
112887     invalidateDrop: function() {
112888         this.valid = false;
112889         this.hideIndicators();
112890     },
112891
112892     onNodeOver: function(node, dragZone, e, data) {
112893         if (data.header.el.dom !== node) {
112894             this.positionIndicator(data.header, node, e);
112895         }
112896         return this.valid ? this.dropAllowed : this.dropNotAllowed;
112897     },
112898
112899     hideIndicators: function() {
112900         this.getTopIndicator().hide();
112901         this.getBottomIndicator().hide();
112902     },
112903
112904     onNodeOut: function() {
112905         this.hideIndicators();
112906     },
112907
112908     onNodeDrop: function(node, dragZone, e, data) {
112909         if (this.valid) {
112910             this.invalidateDrop();
112911             var hd = data.header,
112912                 lastLocation = this.lastLocation,
112913                 fromCt  = hd.ownerCt,
112914                 fromIdx = fromCt.items.indexOf(hd), // Container.items is a MixedCollection
112915                 toCt    = lastLocation.header.ownerCt,
112916                 toIdx   = toCt.items.indexOf(lastLocation.header),
112917                 headerCt = this.headerCt,
112918                 groupCt,
112919                 scrollerOwner;
112920
112921             if (lastLocation.pos === 'after') {
112922                 toIdx++;
112923             }
112924
112925             // If we are dragging in between two HeaderContainers that have had the lockable
112926             // mixin injected we will lock/unlock headers in between sections. Note that lockable
112927             // does NOT currently support grouped headers.
112928             if (fromCt !== toCt && fromCt.lockableInjected && toCt.lockableInjected && toCt.lockedCt) {
112929                 scrollerOwner = fromCt.up('[scrollerOwner]');
112930                 scrollerOwner.lock(hd, toIdx);
112931             } else if (fromCt !== toCt && fromCt.lockableInjected && toCt.lockableInjected && fromCt.lockedCt) {
112932                 scrollerOwner = fromCt.up('[scrollerOwner]');
112933                 scrollerOwner.unlock(hd, toIdx);
112934             } else {
112935                 // If dragging rightwards, then after removal, the insertion index will be one less when moving
112936                 // in between the same container.
112937                 if ((fromCt === toCt) && (toIdx > fromCt.items.indexOf(hd))) {
112938                     toIdx--;
112939                 }
112940
112941                 // Remove dragged header from where it was without destroying it or relaying its Container
112942                 if (fromCt !== toCt) {
112943                     fromCt.suspendLayout = true;
112944                     fromCt.remove(hd, false);
112945                     fromCt.suspendLayout = false;
112946                 }
112947
112948                 // Dragged the last header out of the fromCt group... The fromCt group must die
112949                 if (fromCt.isGroupHeader) {
112950                     if (!fromCt.items.getCount()) {
112951                         groupCt = fromCt.ownerCt;
112952                         groupCt.suspendLayout = true;
112953                         groupCt.remove(fromCt, false);
112954                         fromCt.el.dom.parentNode.removeChild(fromCt.el.dom);
112955                         groupCt.suspendLayout = false;
112956                     } else {
112957                         fromCt.minWidth = fromCt.getWidth() - hd.getWidth();
112958                         fromCt.setWidth(fromCt.minWidth);
112959                     }
112960                 }
112961
112962                 // Move dragged header into its drop position
112963                 toCt.suspendLayout = true;
112964                 if (fromCt === toCt) {
112965                     toCt.move(fromIdx, toIdx);
112966                 } else {
112967                     toCt.insert(toIdx, hd);
112968                 }
112969                 toCt.suspendLayout = false;
112970
112971                 // Group headers acquire the aggregate width of their child headers
112972                 // Therefore a child header may not flex; it must contribute a fixed width.
112973                 // But we restore the flex value when moving back into the main header container
112974                 if (toCt.isGroupHeader) {
112975                     hd.savedFlex = hd.flex;
112976                     delete hd.flex;
112977                     hd.width = hd.getWidth();
112978                     // When there was previously a flex, we need to ensure we don't count for the
112979                     // border twice.
112980                     toCt.minWidth = toCt.getWidth() + hd.getWidth() - (hd.savedFlex ? 1 : 0);
112981                     toCt.setWidth(toCt.minWidth);
112982                 } else {
112983                     if (hd.savedFlex) {
112984                         hd.flex = hd.savedFlex;
112985                         delete hd.width;
112986                     }
112987                 }
112988
112989
112990                 // Refresh columns cache in case we remove an emptied group column
112991                 headerCt.purgeCache();
112992                 headerCt.doLayout();
112993                 headerCt.onHeaderMoved(hd, fromIdx, toIdx);
112994                 // Emptied group header can only be destroyed after the header and grid have been refreshed
112995                 if (!fromCt.items.getCount()) {
112996                     fromCt.destroy();
112997                 }
112998             }
112999         }
113000     }
113001 });
113002
113003
113004 /**
113005  * @class Ext.grid.plugin.Editing
113006
113007 This class provides an abstract grid editing plugin on selected {@link Ext.grid.column.Column columns}.
113008 The editable columns are specified by providing an {@link Ext.grid.column.Column#editor editor}
113009 in the {@link Ext.grid.column.Column column configuration}.
113010
113011 *Note:* This class should not be used directly. See {@link Ext.grid.plugin.CellEditing} and
113012 {@link Ext.grid.plugin.RowEditing}.
113013
113014  * @markdown
113015  */
113016 Ext.define('Ext.grid.plugin.Editing', {
113017     alias: 'editing.editing',
113018
113019     requires: [
113020         'Ext.grid.column.Column',
113021         'Ext.util.KeyNav'
113022     ],
113023
113024     mixins: {
113025         observable: 'Ext.util.Observable'
113026     },
113027
113028     /**
113029      * @cfg {Number} clicksToEdit
113030      * The number of clicks on a grid required to display the editor (defaults to 2).
113031      */
113032     clicksToEdit: 2,
113033
113034     // private
113035     defaultFieldXType: 'textfield',
113036
113037     // cell, row, form
113038     editStyle: '',
113039
113040     constructor: function(config) {
113041         var me = this;
113042         Ext.apply(me, config);
113043
113044         me.addEvents(
113045             // Doc'ed in separate editing plugins
113046             'beforeedit',
113047
113048             // Doc'ed in separate editing plugins
113049             'edit',
113050
113051             // Doc'ed in separate editing plugins
113052             'validateedit'
113053         );
113054         me.mixins.observable.constructor.call(me);
113055         // TODO: Deprecated, remove in 5.0
113056         me.relayEvents(me, ['afteredit'], 'after');
113057     },
113058
113059     // private
113060     init: function(grid) {
113061         var me = this;
113062
113063         me.grid = grid;
113064         me.view = grid.view;
113065         me.initEvents();
113066         me.initFieldAccessors(me.view.getGridColumns());
113067
113068         grid.relayEvents(me, ['beforeedit', 'edit', 'validateedit']);
113069         // Marks the grid as editable, so that the SelectionModel
113070         // can make appropriate decisions during navigation
113071         grid.isEditable = true;
113072         grid.editingPlugin = grid.view.editingPlugin = me;
113073     },
113074
113075     /**
113076      * @private
113077      * AbstractComponent calls destroy on all its plugins at destroy time.
113078      */
113079     destroy: function() {
113080         var me = this,
113081             grid = me.grid,
113082             headerCt = grid.headerCt,
113083             events = grid.events;
113084
113085         Ext.destroy(me.keyNav);
113086         me.removeFieldAccessors(grid.getView().getGridColumns());
113087
113088         // Clear all listeners from all our events, clear all managed listeners we added to other Observables
113089         me.clearListeners();
113090
113091         delete me.grid.editingPlugin;
113092         delete me.grid.view.editingPlugin;
113093         delete me.grid;
113094         delete me.view;
113095         delete me.editor;
113096         delete me.keyNav;
113097     },
113098
113099     // private
113100     getEditStyle: function() {
113101         return this.editStyle;
113102     },
113103
113104     // private
113105     initFieldAccessors: function(column) {
113106         var me = this;
113107
113108         if (Ext.isArray(column)) {
113109             Ext.Array.forEach(column, me.initFieldAccessors, me);
113110             return;
113111         }
113112
113113         // Augment the Header class to have a getEditor and setEditor method
113114         // Important: Only if the header does not have its own implementation.
113115         Ext.applyIf(column, {
113116             getEditor: function(record, defaultField) {
113117                 return me.getColumnField(this, defaultField);
113118             },
113119
113120             setEditor: function(field) {
113121                 me.setColumnField(this, field);
113122             }
113123         });
113124     },
113125
113126     // private
113127     removeFieldAccessors: function(column) {
113128         var me = this;
113129
113130         if (Ext.isArray(column)) {
113131             Ext.Array.forEach(column, me.removeFieldAccessors, me);
113132             return;
113133         }
113134
113135         delete column.getEditor;
113136         delete column.setEditor;
113137     },
113138
113139     // private
113140     // remaps to the public API of Ext.grid.column.Column.getEditor
113141     getColumnField: function(columnHeader, defaultField) {
113142         var field = columnHeader.field;
113143
113144         if (!field && columnHeader.editor) {
113145             field = columnHeader.editor;
113146             delete columnHeader.editor;
113147         }
113148
113149         if (!field && defaultField) {
113150             field = defaultField;
113151         }
113152
113153         if (field) {
113154             if (Ext.isString(field)) {
113155                 field = { xtype: field };
113156             }
113157             if (Ext.isObject(field) && !field.isFormField) {
113158                 field = Ext.ComponentManager.create(field, this.defaultFieldXType);
113159                 columnHeader.field = field;
113160             }
113161
113162             Ext.apply(field, {
113163                 name: columnHeader.dataIndex
113164             });
113165
113166             return field;
113167         }
113168     },
113169
113170     // private
113171     // remaps to the public API of Ext.grid.column.Column.setEditor
113172     setColumnField: function(column, field) {
113173         if (Ext.isObject(field) && !field.isFormField) {
113174             field = Ext.ComponentManager.create(field, this.defaultFieldXType);
113175         }
113176         column.field = field;
113177     },
113178
113179     // private
113180     initEvents: function() {
113181         var me = this;
113182         me.initEditTriggers();
113183         me.initCancelTriggers();
113184     },
113185
113186     // @abstract
113187     initCancelTriggers: Ext.emptyFn,
113188
113189     // private
113190     initEditTriggers: function() {
113191         var me = this,
113192             view = me.view,
113193             clickEvent = me.clicksToEdit === 1 ? 'click' : 'dblclick';
113194
113195         // Start editing
113196         me.mon(view, 'cell' + clickEvent, me.startEditByClick, me);
113197         view.on('render', function() {
113198             me.keyNav = Ext.create('Ext.util.KeyNav', view.el, {
113199                 enter: me.onEnterKey,
113200                 esc: me.onEscKey,
113201                 scope: me
113202             });
113203         }, me, { single: true });
113204     },
113205
113206     // private
113207     onEnterKey: function(e) {
113208         var me = this,
113209             grid = me.grid,
113210             selModel = grid.getSelectionModel(),
113211             record,
113212             columnHeader = grid.headerCt.getHeaderAtIndex(0);
113213
113214         // Calculate editing start position from SelectionModel
113215         // CellSelectionModel
113216         if (selModel.getCurrentPosition) {
113217             pos = selModel.getCurrentPosition();
113218             record = grid.store.getAt(pos.row);
113219             columnHeader = grid.headerCt.getHeaderAtIndex(pos.column);
113220         }
113221         // RowSelectionModel
113222         else {
113223             record = selModel.getLastSelected();
113224         }
113225         me.startEdit(record, columnHeader);
113226     },
113227
113228     // private
113229     onEscKey: function(e) {
113230         this.cancelEdit();
113231     },
113232
113233     // private
113234     startEditByClick: function(view, cell, colIdx, record, row, rowIdx, e) {
113235         this.startEdit(record, view.getHeaderAtIndex(colIdx));
113236     },
113237
113238     /**
113239      * @private
113240      * @abstract. Template method called before editing begins.
113241      * @param {Object} context The current editing context
113242      * @return {Boolean} Return false to cancel the editing process
113243      */
113244     beforeEdit: Ext.emptyFn,
113245
113246     /**
113247      * Start editing the specified record, using the specified Column definition to define which field is being edited.
113248      * @param {Model} record The Store data record which backs the row to be edited.
113249      * @param {Model} columnHeader The Column object defining the column to be edited.
113250      */
113251     startEdit: function(record, columnHeader) {
113252         var me = this,
113253             context = me.getEditingContext(record, columnHeader);
113254
113255         if (me.beforeEdit(context) === false || me.fireEvent('beforeedit', context) === false || context.cancel) {
113256             return false;
113257         }
113258
113259         me.context = context;
113260         me.editing = true;
113261     },
113262
113263     /**
113264      * @private Collects all information necessary for any subclasses to perform their editing functions.
113265      * @param record
113266      * @param columnHeader
113267      * @returns {Object} The editing context based upon the passed record and column
113268      */
113269     getEditingContext: function(record, columnHeader) {
113270         var me = this,
113271             grid = me.grid,
113272             store = grid.store,
113273             rowIdx,
113274             colIdx,
113275             view = grid.getView(),
113276             value;
113277
113278         // If they'd passed numeric row, column indices, look them up.
113279         if (Ext.isNumber(record)) {
113280             rowIdx = record;
113281             record = store.getAt(rowIdx);
113282         } else {
113283             rowIdx = store.indexOf(record);
113284         }
113285         if (Ext.isNumber(columnHeader)) {
113286             colIdx = columnHeader;
113287             columnHeader = grid.headerCt.getHeaderAtIndex(colIdx);
113288         } else {
113289             colIdx = columnHeader.getIndex();
113290         }
113291
113292         value = record.get(columnHeader.dataIndex);
113293         return {
113294             grid: grid,
113295             record: record,
113296             field: columnHeader.dataIndex,
113297             value: value,
113298             row: view.getNode(rowIdx),
113299             column: columnHeader,
113300             rowIdx: rowIdx,
113301             colIdx: colIdx
113302         };
113303     },
113304
113305     /**
113306      * Cancel any active edit that is in progress.
113307      */
113308     cancelEdit: function() {
113309         this.editing = false;
113310     },
113311
113312     /**
113313      * Complete the edit if there is an active edit in progress.
113314      */
113315     completeEdit: function() {
113316         var me = this;
113317
113318         if (me.editing && me.validateEdit()) {
113319             me.fireEvent('edit', me.context);
113320         }
113321
113322         delete me.context;
113323         me.editing = false;
113324     },
113325
113326     // @abstract
113327     validateEdit: function() {
113328         var me = this,
113329             context = me.context;
113330
113331         return me.fireEvent('validateedit', me, context) !== false && !context.cancel;
113332     }
113333 });
113334 /**
113335  * @class Ext.grid.plugin.CellEditing
113336  * @extends Ext.grid.plugin.Editing
113337  *
113338  * The Ext.grid.plugin.CellEditing plugin injects editing at a cell level for a Grid. Only a single
113339  * cell will be editable at a time. The field that will be used for the editor is defined at the
113340  * {@link Ext.grid.column.Column#field field}. The editor can be a field instance or a field configuration.
113341  *
113342  * If an editor is not specified for a particular column then that cell will not be editable and it will
113343  * be skipped when activated via the mouse or the keyboard.
113344  *
113345  * The editor may be shared for each column in the grid, or a different one may be specified for each column.
113346  * An appropriate field type should be chosen to match the data structure that it will be editing. For example,
113347  * to edit a date, it would be useful to specify {@link Ext.form.field.Date} as the editor.
113348  *
113349  * {@img Ext.grid.plugin.CellEditing/Ext.grid.plugin.CellEditing.png Ext.grid.plugin.CellEditing plugin}
113350  *
113351  * ## Example Usage
113352  *    Ext.create('Ext.data.Store', {
113353  *        storeId:'simpsonsStore',
113354  *        fields:['name', 'email', 'phone'],
113355  *        data:{'items':[
113356  *            {"name":"Lisa", "email":"lisa@simpsons.com", "phone":"555-111-1224"},
113357  *            {"name":"Bart", "email":"bart@simpsons.com", "phone":"555--222-1234"},
113358  *            {"name":"Homer", "email":"home@simpsons.com", "phone":"555-222-1244"},
113359  *            {"name":"Marge", "email":"marge@simpsons.com", "phone":"555-222-1254"}
113360  *        ]},
113361  *        proxy: {
113362  *            type: 'memory',
113363  *            reader: {
113364  *                type: 'json',
113365  *                root: 'items'
113366  *            }
113367  *        }
113368  *    });
113369  *
113370  *    Ext.create('Ext.grid.Panel', {
113371  *        title: 'Simpsons',
113372  *        store: Ext.data.StoreManager.lookup('simpsonsStore'),
113373  *        columns: [
113374  *            {header: 'Name',  dataIndex: 'name', field: 'textfield'},
113375  *            {header: 'Email', dataIndex: 'email', flex:1,
113376  *                editor: {
113377  *                    xtype:'textfield',
113378  *                    allowBlank:false
113379  *                }
113380  *            },
113381  *            {header: 'Phone', dataIndex: 'phone'}
113382  *        ],
113383  *        selType: 'cellmodel',
113384  *        plugins: [
113385  *            Ext.create('Ext.grid.plugin.CellEditing', {
113386  *                clicksToEdit: 1
113387  *            })
113388  *        ],
113389  *        height: 200,
113390  *        width: 400,
113391  *        renderTo: Ext.getBody()
113392  *    });
113393  *
113394  */
113395 Ext.define('Ext.grid.plugin.CellEditing', {
113396     alias: 'plugin.cellediting',
113397     extend: 'Ext.grid.plugin.Editing',
113398     requires: ['Ext.grid.CellEditor'],
113399
113400     constructor: function() {
113401         /**
113402          * @event beforeedit
113403          * Fires before cell editing is triggered. The edit event object has the following properties <br />
113404          * <ul style="padding:5px;padding-left:16px;">
113405          * <li>grid - The grid</li>
113406          * <li>record - The record being edited</li>
113407          * <li>field - The field name being edited</li>
113408          * <li>value - The value for the field being edited.</li>
113409          * <li>row - The grid table row</li>
113410          * <li>column - The grid {@link Ext.grid.column.Column Column} defining the column that is being edited.</li>
113411          * <li>rowIdx - The row index that is being edited</li>
113412          * <li>colIdx - The column index that is being edited</li>
113413          * <li>cancel - Set this to true to cancel the edit or return false from your handler.</li>
113414          * </ul>
113415          * @param {Ext.grid.plugin.Editing} editor
113416          * @param {Object} e An edit event (see above for description)
113417          */
113418         /**
113419          * @event edit
113420          * Fires after a cell is edited. The edit event object has the following properties <br />
113421          * <ul style="padding:5px;padding-left:16px;">
113422          * <li>grid - The grid</li>
113423          * <li>record - The record that was edited</li>
113424          * <li>field - The field name that was edited</li>
113425          * <li>value - The value being set</li>
113426          * <li>originalValue - The original value for the field, before the edit.</li>
113427          * <li>row - The grid table row</li>
113428          * <li>column - The grid {@link Ext.grid.column.Column Column} defining the column that was edited.</li>
113429          * <li>rowIdx - The row index that was edited</li>
113430          * <li>colIdx - The column index that was edited</li>
113431          * </ul>
113432          *
113433          * <pre><code>
113434 grid.on('edit', onEdit, this);
113435
113436 function onEdit(e) {
113437     // execute an XHR to send/commit data to the server, in callback do (if successful):
113438     e.record.commit();
113439 };
113440          * </code></pre>
113441          * @param {Ext.grid.plugin.Editing} editor
113442          * @param {Object} e An edit event (see above for description)
113443          */
113444         /**
113445          * @event validateedit
113446          * Fires after a cell is edited, but before the value is set in the record. Return false
113447          * to cancel the change. The edit event object has the following properties <br />
113448          * <ul style="padding:5px;padding-left:16px;">
113449          * <li>grid - The grid</li>
113450          * <li>record - The record being edited</li>
113451          * <li>field - The field name being edited</li>
113452          * <li>value - The value being set</li>
113453          * <li>originalValue - The original value for the field, before the edit.</li>
113454          * <li>row - The grid table row</li>
113455          * <li>column - The grid {@link Ext.grid.column.Column Column} defining the column that is being edited.</li>
113456          * <li>rowIdx - The row index that is being edited</li>
113457          * <li>colIdx - The column index that is being edited</li>
113458          * <li>cancel - Set this to true to cancel the edit or return false from your handler.</li>
113459          * </ul>
113460          * Usage example showing how to remove the red triangle (dirty record indicator) from some
113461          * records (not all).  By observing the grid's validateedit event, it can be cancelled if
113462          * the edit occurs on a targeted row (for example) and then setting the field's new value
113463          * in the Record directly:
113464          * <pre><code>
113465 grid.on('validateedit', function(e) {
113466   var myTargetRow = 6;
113467
113468   if (e.row == myTargetRow) {
113469     e.cancel = true;
113470     e.record.data[e.field] = e.value;
113471   }
113472 });
113473          * </code></pre>
113474          * @param {Ext.grid.plugin.Editing} editor
113475          * @param {Object} e An edit event (see above for description)
113476          */
113477         this.callParent(arguments);
113478         this.editors = Ext.create('Ext.util.MixedCollection', false, function(editor) {
113479             return editor.editorId;
113480         });
113481     },
113482
113483     /**
113484      * @private
113485      * AbstractComponent calls destroy on all its plugins at destroy time.
113486      */
113487     destroy: function() {
113488         var me = this;
113489         me.editors.each(Ext.destroy, Ext);
113490         me.editors.clear();
113491         me.callParent(arguments);
113492     },
113493
113494     // private
113495     // Template method called from base class's initEvents
113496     initCancelTriggers: function() {
113497         var me   = this;
113498             grid = me.grid,
113499             view   = grid.view;
113500
113501         me.mon(view, {
113502             mousewheel: {
113503                 element: 'el',
113504                 fn: me.cancelEdit,
113505                 scope: me
113506             }
113507         });
113508         me.mon(grid, {
113509             columnresize: me.cancelEdit,
113510             columnmove: me.cancelEdit,
113511             scope: me
113512         });
113513     },
113514
113515     /**
113516      * Start editing the specified record, using the specified Column definition to define which field is being edited.
113517      * @param {Model} record The Store data record which backs the row to be edited.
113518      * @param {Model} columnHeader The Column object defining the column to be edited.
113519      * @override
113520      */
113521     startEdit: function(record, columnHeader) {
113522         var me = this,
113523             ed   = me.getEditor(record, columnHeader),
113524             value = record.get(columnHeader.dataIndex),
113525             context = me.getEditingContext(record, columnHeader);
113526
113527         record = context.record;
113528         columnHeader = context.column;
113529
113530         // Complete the edit now, before getting the editor's target
113531         // cell DOM element. Completing the edit causes a view refresh.
113532         me.completeEdit();
113533
113534         // See if the field is editable for the requested record
113535         if (columnHeader && !columnHeader.getEditor(record)) {
113536             return false;
113537         }
113538
113539         if (ed) {
113540             context.originalValue = context.value = value;
113541             if (me.beforeEdit(context) === false || me.fireEvent('beforeedit', context) === false || context.cancel) {
113542                 return false;
113543             }
113544
113545             me.context = context;
113546             me.setActiveEditor(ed);
113547             me.setActiveRecord(record);
113548             me.setActiveColumn(columnHeader);
113549
113550             // Defer, so we have some time between view scroll to sync up the editor
113551             Ext.defer(ed.startEdit, 15, ed, [me.getCell(record, columnHeader), value]);
113552         } else {
113553             // BrowserBug: WebKit & IE refuse to focus the element, rather
113554             // it will focus it and then immediately focus the body. This
113555             // temporary hack works for Webkit and IE6. IE7 and 8 are still
113556             // broken
113557             me.grid.getView().el.focus((Ext.isWebKit || Ext.isIE) ? 10 : false);
113558         }
113559     },
113560
113561     completeEdit: function() {
113562         var activeEd = this.getActiveEditor();
113563         if (activeEd) {
113564             activeEd.completeEdit();
113565         }
113566     },
113567
113568     // internal getters/setters
113569     setActiveEditor: function(ed) {
113570         this.activeEditor = ed;
113571     },
113572
113573     getActiveEditor: function() {
113574         return this.activeEditor;
113575     },
113576
113577     setActiveColumn: function(column) {
113578         this.activeColumn = column;
113579     },
113580
113581     getActiveColumn: function() {
113582         return this.activeColumn;
113583     },
113584
113585     setActiveRecord: function(record) {
113586         this.activeRecord = record;
113587     },
113588
113589     getActiveRecord: function() {
113590         return this.activeRecord;
113591     },
113592
113593     getEditor: function(record, column) {
113594         var editors = this.editors,
113595             editorId = column.itemId || column.id,
113596             editor = editors.getByKey(editorId);
113597
113598         if (editor) {
113599             return editor;
113600         } else {
113601             editor = column.getEditor(record);
113602             if (!editor) {
113603                 return false;
113604             }
113605
113606             // Allow them to specify a CellEditor in the Column
113607             if (!(editor instanceof Ext.grid.CellEditor)) {
113608                 editor = Ext.create('Ext.grid.CellEditor', {
113609                     editorId: editorId,
113610                     field: editor
113611                 });
113612             }
113613             editor.parentEl = this.grid.getEditorParent();
113614             // editor.parentEl should be set here.
113615             editor.on({
113616                 scope: this,
113617                 specialkey: this.onSpecialKey,
113618                 complete: this.onEditComplete,
113619                 canceledit: this.cancelEdit
113620             });
113621             editors.add(editor);
113622             return editor;
113623         }
113624     },
113625
113626     /**
113627      * Get the cell (td) for a particular record and column.
113628      * @param {Ext.data.Model} record
113629      * @param {Ext.grid.column.Colunm} column
113630      * @private
113631      */
113632     getCell: function(record, column) {
113633         return this.grid.getView().getCell(record, column);
113634     },
113635
113636     onSpecialKey: function(ed, field, e) {
113637         var grid = this.grid,
113638             sm;
113639         if (e.getKey() === e.TAB) {
113640             e.stopEvent();
113641             sm = grid.getSelectionModel();
113642             if (sm.onEditorTab) {
113643                 sm.onEditorTab(this, e);
113644             }
113645         }
113646     },
113647
113648     onEditComplete : function(ed, value, startValue) {
113649         var me = this,
113650             grid = me.grid,
113651             sm = grid.getSelectionModel(),
113652             dataIndex = me.getActiveColumn().dataIndex;
113653
113654         me.setActiveEditor(null);
113655         me.setActiveColumn(null);
113656         me.setActiveRecord(null);
113657         delete sm.wasEditing;
113658
113659         if (!me.validateEdit()) {
113660             return;
113661         }
113662         me.context.record.set(dataIndex, value);
113663         me.fireEvent('edit', me, me.context);
113664     },
113665
113666     /**
113667      * Cancel any active editing.
113668      */
113669     cancelEdit: function() {
113670         var me = this,
113671             activeEd = me.getActiveEditor(),
113672             viewEl = me.grid.getView().el;
113673
113674         me.setActiveEditor(null);
113675         me.setActiveColumn(null);
113676         me.setActiveRecord(null);
113677         if (activeEd) {
113678             activeEd.cancelEdit();
113679             viewEl.focus();
113680         }
113681     },
113682
113683     /**
113684      * Starts editing by position (row/column)
113685      * @param {Object} position A position with keys of row and column.
113686      */
113687     startEditByPosition: function(position) {
113688         var me = this,
113689             grid = me.grid,
113690             sm = grid.getSelectionModel(),
113691             editRecord = grid.store.getAt(position.row),
113692             editColumnHeader = grid.headerCt.getHeaderAtIndex(position.column);
113693
113694         if (sm.selectByPosition) {
113695             sm.selectByPosition(position);
113696         }
113697         me.startEdit(editRecord, editColumnHeader);
113698     }
113699 });
113700 /**
113701  * @class Ext.grid.plugin.DragDrop
113702  * <p>This plugin provides drag and/or drop functionality for a GridView.</p>
113703  * <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}
113704  * and loads the data object which is passed to a cooperating {@link Ext.dd.DragZone DragZone}'s methods with the following properties:<ul>
113705  * <li>copy : Boolean
113706  *  <div class="sub-desc">The value of the GridView's <code>copy</code> property, or <code>true</code> if the GridView was configured
113707  *  with <code>allowCopy: true</code> <u>and</u> the control key was pressed when the drag operation was begun.</div></li>
113708  * <li>view : GridView
113709  *  <div class="sub-desc">The source GridView from which the drag originated.</div></li>
113710  * <li>ddel : HtmlElement
113711  *  <div class="sub-desc">The drag proxy element which moves with the mouse</div></li>
113712  * <li>item : HtmlElement
113713  *  <div class="sub-desc">The GridView node upon which the mousedown event was registered.</div></li>
113714  * <li>records : Array
113715  *  <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>
113716  * </ul></p>
113717  * <p>It also creates a specialized instance of {@link Ext.dd.DropZone} which cooperates with other DropZones which are members of the same
113718  * ddGroup which processes such data objects.</p>
113719  * <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
113720  * <code>{@link #event-drop drop}</code></p>
113721  */
113722 Ext.define('Ext.grid.plugin.DragDrop', {
113723     extend: 'Ext.AbstractPlugin',
113724     alias: 'plugin.gridviewdragdrop',
113725
113726     uses: [
113727         'Ext.view.DragZone',
113728         'Ext.grid.ViewDropZone'
113729     ],
113730
113731     /**
113732      * @event beforedrop
113733      * <p><b>This event is fired through the GridView. Add listeners to the GridView object</b></p>
113734      * <p>Fired when a drop gesture has been triggered by a mouseup event in a valid drop position in the GridView.
113735      * @param {HtmlElement} node The GridView node <b>if any</b> over which the mouse was positioned.</p>
113736      * <p>Returning <code>false</code> to this event signals that the drop gesture was invalid, and if the drag proxy
113737      * will animate back to the point from which the drag began.</p>
113738      * <p>Returning <code>0</code> To this event signals that the data transfer operation should not take place, but
113739      * that the gesture was valid, and that the repair operation should not take place.</p>
113740      * <p>Any other return value continues with the data transfer operation.</p>
113741      * @param {Object} data The data object gathered at mousedown time by the cooperating {@link Ext.dd.DragZone DragZone}'s
113742      * {@link Ext.dd.DragZone#getDragData getDragData} method it contains the following properties:<ul>
113743      * <li>copy : Boolean
113744      *  <div class="sub-desc">The value of the GridView's <code>copy</code> property, or <code>true</code> if the GridView was configured
113745      *  with <code>allowCopy: true</code> and the control key was pressed when the drag operation was begun</div></li>
113746      * <li>view : GridView
113747      *  <div class="sub-desc">The source GridView from which the drag originated.</div></li>
113748      * <li>ddel : HtmlElement
113749      *  <div class="sub-desc">The drag proxy element which moves with the mouse</div></li>
113750      * <li>item : HtmlElement
113751      *  <div class="sub-desc">The GridView node upon which the mousedown event was registered.</div></li>
113752      * <li>records : Array
113753      *  <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>
113754      * </ul>
113755      * @param {Ext.data.Model} overModel The Model over which the drop gesture took place.
113756      * @param {String} dropPosition <code>"before"</code> or <code>"after"</code> depending on whether the mouse is above or below the midline of the node.
113757      * @param {Function} dropFunction <p>A function to call to complete the data transfer operation and either move or copy Model instances from the source
113758      * View's Store to the destination View's Store.</p>
113759      * <p>This is useful when you want to perform some kind of asynchronous processing before confirming
113760      * the drop, such as an {@link Ext.window.MessageBox#confirm confirm} call, or an Ajax request.</p>
113761      * <p>Return <code>0</code> from this event handler, and call the <code>dropFunction</code> at any time to perform the data transfer.</p>
113762      */
113763
113764     /**
113765      * @event drop
113766      * <b>This event is fired through the GridView. Add listeners to the GridView object</b>
113767      * Fired when a drop operation has been completed and the data has been moved or copied.
113768      * @param {HtmlElement} node The GridView node <b>if any</b> over which the mouse was positioned.
113769      * @param {Object} data The data object gathered at mousedown time by the cooperating {@link Ext.dd.DragZone DragZone}'s
113770      * {@link Ext.dd.DragZone#getDragData getDragData} method it contains the following properties:<ul>
113771      * <li>copy : Boolean
113772      *  <div class="sub-desc">The value of the GridView's <code>copy</code> property, or <code>true</code> if the GridView was configured
113773      *  with <code>allowCopy: true</code> and the control key was pressed when the drag operation was begun</div></li>
113774      * <li>view : GridView
113775      *  <div class="sub-desc">The source GridView from which the drag originated.</div></li>
113776      * <li>ddel : HtmlElement
113777      *  <div class="sub-desc">The drag proxy element which moves with the mouse</div></li>
113778      * <li>item : HtmlElement
113779      *  <div class="sub-desc">The GridView node upon which the mousedown event was registered.</div></li>
113780      * <li>records : Array
113781      *  <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>
113782      * </ul>
113783      * @param {Ext.data.Model} overModel The Model over which the drop gesture took place.
113784      * @param {String} dropPosition <code>"before"</code> or <code>"after"</code> depending on whether the mouse is above or below the midline of the node.
113785      */
113786
113787     dragText : '{0} selected row{1}',
113788
113789     /**
113790      * @cfg {String} ddGroup
113791      * A named drag drop group to which this object belongs.  If a group is specified, then both the DragZones and DropZone
113792      * used by this plugin will only interact with other drag drop objects in the same group (defaults to 'TreeDD').
113793      */
113794     ddGroup : "GridDD",
113795
113796     /**
113797      * @cfg {String} dragGroup
113798      * <p>The ddGroup to which the DragZone will belong.</p>
113799      * <p>This defines which other DropZones the DragZone will interact with. Drag/DropZones only interact with other Drag/DropZones
113800      * which are members of the same ddGroup.</p>
113801      */
113802
113803     /**
113804      * @cfg {String} dropGroup
113805      * <p>The ddGroup to which the DropZone will belong.</p>
113806      * <p>This defines which other DragZones the DropZone will interact with. Drag/DropZones only interact with other Drag/DropZones
113807      * which are members of the same ddGroup.</p>
113808      */
113809
113810     /**
113811      * @cfg {Boolean} enableDrop
113812      * <p>Defaults to <code>true</code></p>
113813      * <p>Set to <code>false</code> to disallow the View from accepting drop gestures</p>
113814      */
113815     enableDrop: true,
113816
113817     /**
113818      * @cfg {Boolean} enableDrag
113819      * <p>Defaults to <code>true</code></p>
113820      * <p>Set to <code>false</code> to disallow dragging items from the View </p>
113821      */
113822     enableDrag: true,
113823
113824     init : function(view) {
113825         view.on('render', this.onViewRender, this, {single: true});
113826     },
113827
113828     /**
113829      * @private
113830      * AbstractComponent calls destroy on all its plugins at destroy time.
113831      */
113832     destroy: function() {
113833         Ext.destroy(this.dragZone, this.dropZone);
113834     },
113835
113836     onViewRender : function(view) {
113837         var me = this;
113838
113839         if (me.enableDrag) {
113840             me.dragZone = Ext.create('Ext.view.DragZone', {
113841                 view: view,
113842                 ddGroup: me.dragGroup || me.ddGroup,
113843                 dragText: me.dragText
113844             });
113845         }
113846
113847         if (me.enableDrop) {
113848             me.dropZone = Ext.create('Ext.grid.ViewDropZone', {
113849                 view: view,
113850                 ddGroup: me.dropGroup || me.ddGroup
113851             });
113852         }
113853     }
113854 });
113855 /**
113856  * @class Ext.grid.plugin.HeaderReorderer
113857  * @extends Ext.util.Observable
113858  * @private
113859  */
113860 Ext.define('Ext.grid.plugin.HeaderReorderer', {
113861     extend: 'Ext.util.Observable',
113862     requires: ['Ext.grid.header.DragZone', 'Ext.grid.header.DropZone'],
113863     alias: 'plugin.gridheaderreorderer',
113864
113865     init: function(headerCt) {
113866         this.headerCt = headerCt;
113867         headerCt.on('render', this.onHeaderCtRender, this);
113868     },
113869
113870     /**
113871      * @private
113872      * AbstractComponent calls destroy on all its plugins at destroy time.
113873      */
113874     destroy: function() {
113875         Ext.destroy(this.dragZone, this.dropZone);
113876     },
113877
113878     onHeaderCtRender: function() {
113879         this.dragZone = Ext.create('Ext.grid.header.DragZone', this.headerCt);
113880         this.dropZone = Ext.create('Ext.grid.header.DropZone', this.headerCt);
113881         if (this.disabled) {
113882             this.dragZone.disable();
113883         }
113884     },
113885     
113886     enable: function() {
113887         this.disabled = false;
113888         if (this.dragZone) {
113889             this.dragZone.enable();
113890         }
113891     },
113892     
113893     disable: function() {
113894         this.disabled = true;
113895         if (this.dragZone) {
113896             this.dragZone.disable();
113897         }
113898     }
113899 });
113900 /**
113901  * @class Ext.grid.plugin.HeaderResizer
113902  * @extends Ext.util.Observable
113903  * 
113904  * Plugin to add header resizing functionality to a HeaderContainer.
113905  * Always resizing header to the left of the splitter you are resizing.
113906  * 
113907  * Todo: Consider RTL support, columns would always calculate to the right of
113908  *    the splitter instead of to the left.
113909  */
113910 Ext.define('Ext.grid.plugin.HeaderResizer', {
113911     extend: 'Ext.util.Observable',
113912     requires: ['Ext.dd.DragTracker', 'Ext.util.Region'],
113913     alias: 'plugin.gridheaderresizer',
113914     
113915     disabled: false,
113916
113917     /**
113918      * @cfg {Boolean} dynamic
113919      * Set to true to resize on the fly rather than using a proxy marker. Defaults to false.
113920      */
113921     configs: {
113922         dynamic: true
113923     },
113924
113925     colHeaderCls: Ext.baseCSSPrefix + 'column-header',
113926
113927     minColWidth: 40,
113928     maxColWidth: 1000,
113929     wResizeCursor: 'col-resize',
113930     eResizeCursor: 'col-resize',
113931     // not using w and e resize bc we are only ever resizing one
113932     // column
113933     //wResizeCursor: Ext.isWebKit ? 'w-resize' : 'col-resize',
113934     //eResizeCursor: Ext.isWebKit ? 'e-resize' : 'col-resize',
113935
113936     init: function(headerCt) {
113937         this.headerCt = headerCt;
113938         headerCt.on('render', this.afterHeaderRender, this, {single: true});
113939     },
113940
113941     /**
113942      * @private
113943      * AbstractComponent calls destroy on all its plugins at destroy time.
113944      */
113945     destroy: function() {
113946         if (this.tracker) {
113947             this.tracker.destroy();
113948         }
113949     },
113950
113951     afterHeaderRender: function() {
113952         var headerCt = this.headerCt,
113953             el = headerCt.el;
113954
113955         headerCt.mon(el, 'mousemove', this.onHeaderCtMouseMove, this);
113956
113957         this.tracker = Ext.create('Ext.dd.DragTracker', {
113958             disabled: this.disabled,
113959             onBeforeStart: Ext.Function.bind(this.onBeforeStart, this),
113960             onStart: Ext.Function.bind(this.onStart, this),
113961             onDrag: Ext.Function.bind(this.onDrag, this),
113962             onEnd: Ext.Function.bind(this.onEnd, this),
113963             tolerance: 3,
113964             autoStart: 300,
113965             el: el
113966         });
113967     },
113968
113969     // As we mouse over individual headers, change the cursor to indicate
113970     // that resizing is available, and cache the resize target header for use
113971     // if/when they mousedown.
113972     onHeaderCtMouseMove: function(e, t) {
113973         if (this.headerCt.dragging) {
113974             if (this.activeHd) {
113975                 this.activeHd.el.dom.style.cursor = '';
113976                 delete this.activeHd;
113977             }
113978         } else {
113979             var headerEl = e.getTarget('.' + this.colHeaderCls, 3, true),
113980                 overHeader, resizeHeader;
113981
113982             if (headerEl){
113983                 overHeader = Ext.getCmp(headerEl.id);
113984
113985                 // On left edge, we are resizing the previous non-hidden, base level column.
113986                 if (overHeader.isOnLeftEdge(e)) {
113987                     resizeHeader = overHeader.previousNode('gridcolumn:not([hidden]):not([isGroupHeader])');
113988                 }
113989                 // Else, if on the right edge, we're resizing the column we are over
113990                 else if (overHeader.isOnRightEdge(e)) {
113991                     resizeHeader = overHeader;
113992                 }
113993                 // Between the edges: we are not resizing
113994                 else {
113995                     resizeHeader = null;
113996                 }
113997
113998                 // We *are* resizing
113999                 if (resizeHeader) {
114000                     // If we're attempting to resize a group header, that cannot be resized,
114001                     // so find its last base level column header; Group headers are sized
114002                     // by the size of their child headers.
114003                     if (resizeHeader.isGroupHeader) {
114004                         resizeHeader = resizeHeader.getVisibleGridColumns();
114005                         resizeHeader = resizeHeader[resizeHeader.length - 1];
114006                     }
114007
114008                     if (resizeHeader && !resizeHeader.fixed) {
114009                         this.activeHd = resizeHeader;
114010                         overHeader.el.dom.style.cursor = this.eResizeCursor;
114011                     }
114012                 // reset
114013                 } else {
114014                     overHeader.el.dom.style.cursor = '';
114015                     delete this.activeHd;
114016                 }
114017             }
114018         }
114019     },
114020
114021     // only start when there is an activeHd
114022     onBeforeStart : function(e){
114023         var t = e.getTarget();
114024         // cache the activeHd because it will be cleared.
114025         this.dragHd = this.activeHd;
114026
114027         if (!!this.dragHd && !Ext.fly(t).hasCls('x-column-header-trigger') && !this.headerCt.dragging) {
114028             //this.headerCt.dragging = true;
114029             this.tracker.constrainTo = this.getConstrainRegion();
114030             return true;
114031         } else {
114032             this.headerCt.dragging = false;
114033             return false;
114034         }
114035     },
114036
114037     // get the region to constrain to, takes into account max and min col widths
114038     getConstrainRegion: function() {
114039         var dragHdEl = this.dragHd.el,
114040             region   = Ext.util.Region.getRegion(dragHdEl);
114041
114042         return region.adjust(
114043             0,
114044             this.maxColWidth - dragHdEl.getWidth(),
114045             0,
114046             this.minColWidth
114047         );
114048     },
114049
114050     // initialize the left and right hand side markers around
114051     // the header that we are resizing
114052     onStart: function(e){
114053         var me       = this,
114054             dragHd   = me.dragHd,
114055             dragHdEl = dragHd.el,
114056             width    = dragHdEl.getWidth(),
114057             headerCt = me.headerCt,
114058             t        = e.getTarget();
114059
114060         if (me.dragHd && !Ext.fly(t).hasCls('x-column-header-trigger')) {
114061             headerCt.dragging = true;
114062         }
114063
114064         me.origWidth = width;
114065
114066         // setup marker proxies
114067         if (!me.dynamic) {
114068             var xy           = dragHdEl.getXY(),
114069                 gridSection  = headerCt.up('[scrollerOwner]'),
114070                 dragHct      = me.dragHd.up(':not([isGroupHeader])'),
114071                 firstSection = dragHct.up(),
114072                 lhsMarker    = gridSection.getLhsMarker(),
114073                 rhsMarker    = gridSection.getRhsMarker(),
114074                 el           = rhsMarker.parent(),
114075                 offsetLeft   = el.getLeft(true),
114076                 offsetTop    = el.getTop(true),
114077                 topLeft      = el.translatePoints(xy),
114078                 markerHeight = firstSection.body.getHeight() + headerCt.getHeight(),
114079                 top = topLeft.top - offsetTop;
114080
114081             lhsMarker.setTop(top);
114082             rhsMarker.setTop(top);
114083             lhsMarker.setHeight(markerHeight);
114084             rhsMarker.setHeight(markerHeight);
114085             lhsMarker.setLeft(topLeft.left - offsetLeft);
114086             rhsMarker.setLeft(topLeft.left + width - offsetLeft);
114087         }
114088     },
114089
114090     // synchronize the rhsMarker with the mouse movement
114091     onDrag: function(e){
114092         if (!this.dynamic) {
114093             var xy          = this.tracker.getXY('point'),
114094                 gridSection = this.headerCt.up('[scrollerOwner]'),
114095                 rhsMarker   = gridSection.getRhsMarker(),
114096                 el          = rhsMarker.parent(),
114097                 topLeft     = el.translatePoints(xy),
114098                 offsetLeft  = el.getLeft(true);
114099
114100             rhsMarker.setLeft(topLeft.left - offsetLeft);
114101         // Resize as user interacts
114102         } else {
114103             this.doResize();
114104         }
114105     },
114106
114107     onEnd: function(e){
114108         this.headerCt.dragging = false;
114109         if (this.dragHd) {
114110             if (!this.dynamic) {
114111                 var dragHd      = this.dragHd,
114112                     gridSection = this.headerCt.up('[scrollerOwner]'),
114113                     lhsMarker   = gridSection.getLhsMarker(),
114114                     rhsMarker   = gridSection.getRhsMarker(),
114115                     currWidth   = dragHd.getWidth(),
114116                     offset      = this.tracker.getOffset('point'),
114117                     offscreen   = -9999;
114118
114119                 // hide markers
114120                 lhsMarker.setLeft(offscreen);
114121                 rhsMarker.setLeft(offscreen);
114122             }
114123             this.doResize();
114124         }
114125     },
114126
114127     doResize: function() {
114128         if (this.dragHd) {
114129             var dragHd = this.dragHd,
114130                 nextHd,
114131                 offset = this.tracker.getOffset('point');
114132
114133             // resize the dragHd
114134             if (dragHd.flex) {
114135                 delete dragHd.flex;
114136             }
114137
114138             // If HeaderContainer is configured forceFit, inhibit upstream layout notification, so that
114139             // we can also shrink the following Header by an equal amount, and *then* inform the upstream layout.
114140             if (this.headerCt.forceFit) {
114141                 nextHd = dragHd.nextNode('gridcolumn:not([hidden]):not([isGroupHeader])');
114142                 if (nextHd) {
114143                     this.headerCt.componentLayout.layoutBusy = true;
114144                 }
114145             }
114146
114147             // Non-flexed Headers may never be squeezed in the event of a shortfall so
114148             // always set the minWidth to their current width.
114149             dragHd.minWidth = this.origWidth + offset[0];
114150             dragHd.setWidth(dragHd.minWidth);
114151
114152             // In the case of forceFit, change the following Header width.
114153             // Then apply the two width changes by laying out the owning HeaderContainer
114154             if (nextHd) {
114155                 delete nextHd.flex;
114156                 nextHd.setWidth(nextHd.getWidth() - offset[0]);
114157                 this.headerCt.componentLayout.layoutBusy = false;
114158                 this.headerCt.doComponentLayout();
114159             }
114160         }
114161     },
114162     
114163     disable: function() {
114164         this.disabled = true;
114165         if (this.tracker) {
114166             this.tracker.disable();
114167         }
114168     },
114169     
114170     enable: function() {
114171         this.disabled = false;
114172         if (this.tracker) {
114173             this.tracker.enable();
114174         }
114175     }
114176 });
114177 /**
114178  * @class Ext.grid.plugin.RowEditing
114179  * @extends Ext.grid.plugin.Editing
114180  * 
114181  * The Ext.grid.plugin.RowEditing plugin injects editing at a row level for a Grid. When editing begins,
114182  * a small floating dialog will be shown for the appropriate row. Each editable column will show a field
114183  * for editing. There is a button to save or cancel all changes for the edit.
114184  * 
114185  * The field that will be used for the editor is defined at the
114186  * {@link Ext.grid.column.Column#field field}. The editor can be a field instance or a field configuration.
114187  * If an editor is not specified for a particular column then that column won't be editable and the value of
114188  * the column will be displayed.
114189  * The editor may be shared for each column in the grid, or a different one may be specified for each column.
114190  * An appropriate field type should be chosen to match the data structure that it will be editing. For example,
114191  * to edit a date, it would be useful to specify {@link Ext.form.field.Date} as the editor.
114192  * 
114193  * {@img Ext.grid.plugin.RowEditing/Ext.grid.plugin.RowEditing.png Ext.grid.plugin.RowEditing plugin}
114194  *
114195  * ## Example Usage
114196  *    Ext.create('Ext.data.Store', {
114197  *        storeId:'simpsonsStore',
114198  *        fields:['name', 'email', 'phone'],
114199  *        data:{'items':[
114200  *            {"name":"Lisa", "email":"lisa@simpsons.com", "phone":"555-111-1224"},
114201  *            {"name":"Bart", "email":"bart@simpsons.com", "phone":"555--222-1234"},
114202  *            {"name":"Homer", "email":"home@simpsons.com", "phone":"555-222-1244"},                        
114203  *            {"name":"Marge", "email":"marge@simpsons.com", "phone":"555-222-1254"}            
114204  *        ]},
114205  *        proxy: {
114206  *            type: 'memory',
114207  *            reader: {
114208  *                type: 'json',
114209  *                root: 'items'
114210  *            }
114211  *        }
114212  *    });
114213  *   
114214  *    Ext.create('Ext.grid.Panel', {
114215  *        title: 'Simpsons',
114216  *        store: Ext.data.StoreManager.lookup('simpsonsStore'),
114217  *        columns: [
114218  *            {header: 'Name',  dataIndex: 'name', field: 'textfield'},
114219  *            {header: 'Email', dataIndex: 'email', flex:1, 
114220  *                editor: {
114221  *                    xtype:'textfield',
114222  *                    allowBlank:false
114223  *                }
114224  *            },
114225  *            {header: 'Phone', dataIndex: 'phone'}
114226  *        ],
114227  *        selType: 'rowmodel',
114228  *        plugins: [
114229  *            Ext.create('Ext.grid.plugin.RowEditing', {
114230  *                clicksToEdit: 1
114231  *            })
114232  *        ],
114233  *        height: 200,
114234  *        width: 400,
114235  *        renderTo: Ext.getBody()
114236  *    });
114237  * 
114238  * @markdown
114239  *
114240  */
114241 Ext.define('Ext.grid.plugin.RowEditing', {
114242     extend: 'Ext.grid.plugin.Editing',
114243     alias: 'plugin.rowediting',
114244
114245     requires: [
114246         'Ext.grid.RowEditor'
114247     ],
114248
114249     editStyle: 'row',
114250
114251     /**
114252      * @cfg {Boolean} autoCancel
114253      * `true` to automatically cancel any pending changes when the row editor begins editing a new row.
114254      * `false` to force the user to explicitly cancel the pending changes. Defaults to `true`.
114255      * @markdown
114256      */
114257     autoCancel: true,
114258
114259     /**
114260      * @cfg {Number} clicksToMoveEditor
114261      * The number of clicks to move the row editor to a new row while it is visible and actively editing another row.
114262      * This will default to the same value as {@link Ext.grid.plugin.Editing#clicksToEdit clicksToEdit}.
114263      * @markdown
114264      */
114265
114266     /**
114267      * @cfg {Boolean} errorSummary
114268      * `true` to show a {@link Ext.tip.ToolTip tooltip} that summarizes all validation errors present
114269      * in the row editor. Set to `false` to prevent the tooltip from showing. Defaults to `true`.
114270      * @markdown
114271      */
114272     errorSummary: true,
114273
114274     /**
114275      * @event beforeedit
114276      * Fires before row editing is triggered. The edit event object has the following properties <br />
114277      * <ul style="padding:5px;padding-left:16px;">
114278      * <li>grid - The grid this editor is on</li>
114279      * <li>view - The grid view</li>
114280      * <li>store - The grid store</li>
114281      * <li>record - The record being edited</li>
114282      * <li>row - The grid table row</li>
114283      * <li>column - The grid {@link Ext.grid.column.Column Column} defining the column that initiated the edit</li>
114284      * <li>rowIdx - The row index that is being edited</li>
114285      * <li>colIdx - The column index that initiated the edit</li>
114286      * <li>cancel - Set this to true to cancel the edit or return false from your handler.</li>
114287      * </ul>
114288      * @param {Ext.grid.plugin.Editing} editor
114289      * @param {Object} e An edit event (see above for description)
114290      */
114291     /**
114292      * @event edit
114293      * Fires after a row is edited. The edit event object has the following properties <br />
114294      * <ul style="padding:5px;padding-left:16px;">
114295      * <li>grid - The grid this editor is on</li>
114296      * <li>view - The grid view</li>
114297      * <li>store - The grid store</li>
114298      * <li>record - The record being edited</li>
114299      * <li>row - The grid table row</li>
114300      * <li>column - The grid {@link Ext.grid.column.Column Column} defining the column that initiated the edit</li>
114301      * <li>rowIdx - The row index that is being edited</li>
114302      * <li>colIdx - The column index that initiated the edit</li>
114303      * </ul>
114304      *
114305      * <pre><code>
114306 grid.on('edit', onEdit, this);
114307
114308 function onEdit(e) {
114309     // execute an XHR to send/commit data to the server, in callback do (if successful):
114310     e.record.commit();
114311 };
114312      * </code></pre>
114313      * @param {Ext.grid.plugin.Editing} editor
114314      * @param {Object} e An edit event (see above for description)
114315      */
114316     /**
114317      * @event validateedit
114318      * Fires after a cell is edited, but before the value is set in the record. Return false
114319      * to cancel the change. The edit event object has the following properties <br />
114320      * <ul style="padding:5px;padding-left:16px;">
114321      * <li>grid - The grid this editor is on</li>
114322      * <li>view - The grid view</li>
114323      * <li>store - The grid store</li>
114324      * <li>record - The record being edited</li>
114325      * <li>row - The grid table row</li>
114326      * <li>column - The grid {@link Ext.grid.column.Column Column} defining the column that initiated the edit</li>
114327      * <li>rowIdx - The row index that is being edited</li>
114328      * <li>colIdx - The column index that initiated the edit</li>
114329      * <li>cancel - Set this to true to cancel the edit or return false from your handler.</li>
114330      * </ul>
114331      * Usage example showing how to remove the red triangle (dirty record indicator) from some
114332      * records (not all).  By observing the grid's validateedit event, it can be cancelled if
114333      * the edit occurs on a targeted row (for example) and then setting the field's new value
114334      * in the Record directly:
114335      * <pre><code>
114336 grid.on('validateedit', function(e) {
114337   var myTargetRow = 6;
114338
114339   if (e.rowIdx == myTargetRow) {
114340     e.cancel = true;
114341     e.record.data[e.field] = e.value;
114342   }
114343 });
114344      * </code></pre>
114345      * @param {Ext.grid.plugin.Editing} editor
114346      * @param {Object} e An edit event (see above for description)
114347      */
114348
114349     constructor: function() {
114350         var me = this;
114351         me.callParent(arguments);
114352
114353         if (!me.clicksToMoveEditor) {
114354             me.clicksToMoveEditor = me.clicksToEdit;
114355         }
114356
114357         me.autoCancel = !!me.autoCancel;
114358     },
114359
114360     /**
114361      * @private
114362      * AbstractComponent calls destroy on all its plugins at destroy time.
114363      */
114364     destroy: function() {
114365         var me = this;
114366         Ext.destroy(me.editor);
114367         me.callParent(arguments);
114368     },
114369
114370     /**
114371      * Start editing the specified record, using the specified Column definition to define which field is being edited.
114372      * @param {Model} record The Store data record which backs the row to be edited.
114373      * @param {Model} columnHeader The Column object defining the column to be edited.
114374      * @override
114375      */
114376     startEdit: function(record, columnHeader) {
114377         var me = this,
114378             editor = me.getEditor();
114379
114380         if (me.callParent(arguments) === false) {
114381             return false;
114382         }
114383
114384         // Fire off our editor
114385         if (editor.beforeEdit() !== false) {
114386             editor.startEdit(me.context.record, me.context.column);
114387         }
114388     },
114389
114390     // private
114391     cancelEdit: function() {
114392         var me = this;
114393
114394         if (me.editing) {
114395             me.getEditor().cancelEdit();
114396             me.callParent(arguments);
114397         }
114398     },
114399
114400     // private
114401     completeEdit: function() {
114402         var me = this;
114403
114404         if (me.editing && me.validateEdit()) {
114405             me.editing = false;
114406             me.fireEvent('edit', me.context);
114407         }
114408     },
114409
114410     // private
114411     validateEdit: function() {
114412         var me = this;
114413         return me.callParent(arguments) && me.getEditor().completeEdit();
114414     },
114415
114416     // private
114417     getEditor: function() {
114418         var me = this;
114419
114420         if (!me.editor) {
114421             me.editor = me.initEditor();
114422         }
114423         return me.editor;
114424     },
114425
114426     // private
114427     initEditor: function() {
114428         var me = this,
114429             grid = me.grid,
114430             view = me.view,
114431             headerCt = grid.headerCt;
114432
114433         return Ext.create('Ext.grid.RowEditor', {
114434             autoCancel: me.autoCancel,
114435             errorSummary: me.errorSummary,
114436             fields: headerCt.getGridColumns(),
114437             hidden: true,
114438
114439             // keep a reference..
114440             editingPlugin: me,
114441             renderTo: view.el
114442         });
114443     },
114444
114445     // private
114446     initEditTriggers: function() {
114447         var me = this,
114448             grid = me.grid,
114449             view = me.view,
114450             headerCt = grid.headerCt,
114451             moveEditorEvent = me.clicksToMoveEditor === 1 ? 'click' : 'dblclick';
114452
114453         me.callParent(arguments);
114454
114455         if (me.clicksToMoveEditor !== me.clicksToEdit) {
114456             me.mon(view, 'cell' + moveEditorEvent, me.moveEditorByClick, me);
114457         }
114458
114459         view.on('render', function() {
114460             // Column events
114461             me.mon(headerCt, {
114462                 add: me.onColumnAdd,
114463                 remove: me.onColumnRemove,
114464                 columnresize: me.onColumnResize,
114465                 columnhide: me.onColumnHide,
114466                 columnshow: me.onColumnShow,
114467                 columnmove: me.onColumnMove,
114468                 scope: me
114469             });
114470         }, me, { single: true });
114471     },
114472
114473     startEditByClick: function() {
114474         var me = this;
114475         if (!me.editing || me.clicksToMoveEditor === me.clicksToEdit) {
114476             me.callParent(arguments);
114477         }
114478     },
114479
114480     moveEditorByClick: function() {
114481         var me = this;
114482         if (me.editing) {
114483             me.superclass.startEditByClick.apply(me, arguments);
114484         }
114485     },
114486
114487     // private
114488     onColumnAdd: function(ct, column) {
114489         var me = this,
114490             editor = me.getEditor();
114491
114492         me.initFieldAccessors(column);
114493         if (editor && editor.onColumnAdd) {
114494             editor.onColumnAdd(column);
114495         }
114496     },
114497
114498     // private
114499     onColumnRemove: function(ct, column) {
114500         var me = this,
114501             editor = me.getEditor();
114502
114503         if (editor && editor.onColumnRemove) {
114504             editor.onColumnRemove(column);
114505         }
114506         me.removeFieldAccessors(column);
114507     },
114508
114509     // private
114510     onColumnResize: function(ct, column, width) {
114511         var me = this,
114512             editor = me.getEditor();
114513
114514         if (editor && editor.onColumnResize) {
114515             editor.onColumnResize(column, width);
114516         }
114517     },
114518
114519     // private
114520     onColumnHide: function(ct, column) {
114521         var me = this,
114522             editor = me.getEditor();
114523
114524         if (editor && editor.onColumnHide) {
114525             editor.onColumnHide(column);
114526         }
114527     },
114528
114529     // private
114530     onColumnShow: function(ct, column) {
114531         var me = this,
114532             editor = me.getEditor();
114533
114534         if (editor && editor.onColumnShow) {
114535             editor.onColumnShow(column);
114536         }
114537     },
114538
114539     // private
114540     onColumnMove: function(ct, column, fromIdx, toIdx) {
114541         var me = this,
114542             editor = me.getEditor();
114543
114544         if (editor && editor.onColumnMove) {
114545             editor.onColumnMove(column, fromIdx, toIdx);
114546         }
114547     },
114548
114549     // private
114550     setColumnField: function(column, field) {
114551         var me = this;
114552         me.callParent(arguments);
114553         me.getEditor().setField(column.field, column);
114554     }
114555 });
114556 /**
114557  * @class Ext.grid.property.Grid
114558  * @extends Ext.grid.Panel
114559  * A specialized grid implementation intended to mimic the traditional property grid as typically seen in
114560  * development IDEs.  Each row in the grid represents a property of some object, and the data is stored
114561  * as a set of name/value pairs in {@link Ext.grid.property.Property Properties}.  Example usage:
114562  * <pre><code>
114563 var grid = new Ext.grid.property.Grid({
114564     title: 'Properties Grid',
114565     width: 300,
114566     renderTo: 'grid-ct',
114567     source: {
114568         "(name)": "My Object",
114569         "Created": Ext.Date.parse('10/15/2006', 'm/d/Y'),
114570         "Available": false,
114571         "Version": .01,
114572         "Description": "A test object"
114573     }
114574 });
114575 </code></pre>
114576  * @constructor
114577  * @param {Object} config The grid config object
114578  */
114579 Ext.define('Ext.grid.property.Grid', {
114580
114581     extend: 'Ext.grid.Panel',
114582
114583     alternateClassName: 'Ext.grid.PropertyGrid',
114584
114585     uses: [
114586        'Ext.grid.plugin.CellEditing',
114587        'Ext.grid.property.Store',
114588        'Ext.grid.property.HeaderContainer',
114589        'Ext.XTemplate',
114590        'Ext.grid.CellEditor',
114591        'Ext.form.field.Date',
114592        'Ext.form.field.Text',
114593        'Ext.form.field.Number'
114594     ],
114595
114596    /**
114597     * @cfg {Object} propertyNames An object containing custom property name/display name pairs.
114598     * If specified, the display name will be shown in the name column instead of the property name.
114599     */
114600
114601     /**
114602     * @cfg {Object} source A data object to use as the data source of the grid (see {@link #setSource} for details).
114603     */
114604
114605     /**
114606     * @cfg {Object} customEditors An object containing name/value pairs of custom editor type definitions that allow
114607     * the grid to support additional types of editable fields.  By default, the grid supports strongly-typed editing
114608     * of strings, dates, numbers and booleans using built-in form editors, but any custom type can be supported and
114609     * associated with a custom input control by specifying a custom editor.  The name of the editor
114610     * type should correspond with the name of the property that will use the editor.  Example usage:
114611     * <pre><code>
114612 var grid = new Ext.grid.property.Grid({
114613
114614     // Custom editors for certain property names
114615     customEditors: {
114616         evtStart: Ext.create('Ext.form.TimeField' {selectOnFocus:true})
114617     },
114618
114619     // Displayed name for property names in the source
114620     propertyNames: {
114621         evtStart: 'Start Time'
114622     },
114623
114624     // Data object containing properties to edit
114625     source: {
114626         evtStart: '10:00 AM'
114627     }
114628 });
114629 </code></pre>
114630     */
114631
114632     /**
114633     * @cfg {Object} source A data object to use as the data source of the grid (see {@link #setSource} for details).
114634     */
114635
114636     /**
114637     * @cfg {Object} customRenderers An object containing name/value pairs of custom renderer type definitions that allow
114638     * the grid to support custom rendering of fields.  By default, the grid supports strongly-typed rendering
114639     * of strings, dates, numbers and booleans using built-in form editors, but any custom type can be supported and
114640     * associated with the type of the value.  The name of the renderer type should correspond with the name of the property
114641     * that it will render.  Example usage:
114642     * <pre><code>
114643 var grid = Ext.create('Ext.grid.property.Grid', {
114644     customRenderers: {
114645         Available: function(v){
114646             if (v) {
114647                 return '<span style="color: green;">Yes</span>';
114648             } else {
114649                 return '<span style="color: red;">No</span>';
114650             }
114651         }
114652     },
114653     source: {
114654         Available: true
114655     }
114656 });
114657 </code></pre>
114658     */
114659
114660     /**
114661      * @cfg {String} valueField
114662      * Optional. The name of the field from the property store to use as the value field name. Defaults to <code>'value'</code>
114663      * This may be useful if you do not configure the property Grid from an object, but use your own store configuration.
114664      */
114665     valueField: 'value',
114666
114667     /**
114668      * @cfg {String} nameField
114669      * Optional. The name of the field from the property store to use as the property field name. Defaults to <code>'name'</code>
114670      * This may be useful if you do not configure the property Grid from an object, but use your own store configuration.
114671      */
114672     nameField: 'name',
114673
114674     // private config overrides
114675     enableColumnMove: false,
114676     columnLines: true,
114677     stripeRows: false,
114678     trackMouseOver: false,
114679     clicksToEdit: 1,
114680     enableHdMenu: false,
114681
114682     // private
114683     initComponent : function(){
114684         var me = this;
114685
114686         me.addCls(Ext.baseCSSPrefix + 'property-grid');
114687         me.plugins = me.plugins || [];
114688
114689         // Enable cell editing. Inject a custom startEdit which always edits column 1 regardless of which column was clicked.
114690         me.plugins.push(Ext.create('Ext.grid.plugin.CellEditing', {
114691             clicksToEdit: me.clicksToEdit,
114692
114693             // Inject a startEdit which always edits the value column
114694             startEdit: function(record, column) {
114695                 // Maintainer: Do not change this 'this' to 'me'! It is the CellEditing object's own scope.
114696                 Ext.grid.plugin.CellEditing.prototype.startEdit.call(this, record, me.headerCt.child('#' + me.valueField));
114697             }
114698         }));
114699
114700         me.selModel = {
114701             selType: 'cellmodel',
114702             onCellSelect: function(position) {
114703                 if (position.column != 1) {
114704                     position.column = 1;
114705                     Ext.selection.CellModel.prototype.onCellSelect.call(this, position);
114706                 }
114707             }
114708         };
114709         me.customRenderers = me.customRenderers || {};
114710         me.customEditors = me.customEditors || {};
114711
114712         // Create a property.Store from the source object unless configured with a store
114713         if (!me.store) {
114714             me.propStore = me.store = Ext.create('Ext.grid.property.Store', me, me.source);
114715         }
114716
114717         me.store.sort('name', 'ASC');
114718         me.columns = Ext.create('Ext.grid.property.HeaderContainer', me, me.store);
114719
114720         me.addEvents(
114721             /**
114722              * @event beforepropertychange
114723              * Fires before a property value changes.  Handlers can return false to cancel the property change
114724              * (this will internally call {@link Ext.data.Record#reject} on the property's record).
114725              * @param {Object} source The source data object for the grid (corresponds to the same object passed in
114726              * as the {@link #source} config property).
114727              * @param {String} recordId The record's id in the data store
114728              * @param {Mixed} value The current edited property value
114729              * @param {Mixed} oldValue The original property value prior to editing
114730              */
114731             'beforepropertychange',
114732             /**
114733              * @event propertychange
114734              * Fires after a property value has changed.
114735              * @param {Object} source The source data object for the grid (corresponds to the same object passed in
114736              * as the {@link #source} config property).
114737              * @param {String} recordId The record's id in the data store
114738              * @param {Mixed} value The current edited property value
114739              * @param {Mixed} oldValue The original property value prior to editing
114740              */
114741             'propertychange'
114742         );
114743         me.callParent();
114744
114745         // Inject a custom implementation of walkCells which only goes up or down
114746         me.getView().walkCells = this.walkCells;
114747
114748         // Set up our default editor set for the 4 atomic data types
114749         me.editors = {
114750             'date'    : Ext.create('Ext.grid.CellEditor', { field: Ext.create('Ext.form.field.Date',   {selectOnFocus: true})}),
114751             'string'  : Ext.create('Ext.grid.CellEditor', { field: Ext.create('Ext.form.field.Text',   {selectOnFocus: true})}),
114752             'number'  : Ext.create('Ext.grid.CellEditor', { field: Ext.create('Ext.form.field.Number', {selectOnFocus: true})}),
114753             'boolean' : Ext.create('Ext.grid.CellEditor', { field: Ext.create('Ext.form.field.ComboBox', {
114754                 editable: false,
114755                 store: [[ true, me.headerCt.trueText ], [false, me.headerCt.falseText ]]
114756             })})
114757         };
114758
114759         // Track changes to the data so we can fire our events.
114760         this.store.on('update', me.onUpdate, me);
114761     },
114762
114763     // private
114764     onUpdate : function(store, record, operation) {
114765         var me = this,
114766             v, oldValue;
114767
114768         if (operation == Ext.data.Model.EDIT) {
114769             v = record.get(me.valueField);
114770             oldValue = record.modified.value;
114771             if (me.fireEvent('beforepropertychange', me.source, record.id, v, oldValue) !== false) {
114772                 if (me.source) {
114773                     me.source[record.id] = v;
114774                 }
114775                 record.commit();
114776                 me.fireEvent('propertychange', me.source, record.id, v, oldValue);
114777             } else {
114778                 record.reject();
114779             }
114780         }
114781     },
114782
114783     // Custom implementation of walkCells which only goes up and down.
114784     walkCells: function(pos, direction, e, preventWrap, verifierFn, scope) {
114785         if (direction == 'left') {
114786             direction = 'up';
114787         } else if (direction == 'right') {
114788             direction = 'down';
114789         }
114790         var pos = Ext.view.Table.prototype.walkCells.call(this, pos, direction, e, preventWrap, verifierFn, scope);
114791         if (!pos.column) {
114792             pos.column = 1;
114793         }
114794         return pos;
114795     },
114796
114797     // private
114798     // returns the correct editor type for the property type, or a custom one keyed by the property name
114799     getCellEditor : function(record, column) {
114800         var me = this,
114801             propName = record.get(me.nameField), 
114802             val = record.get(me.valueField),
114803             editor = me.customEditors[propName];
114804
114805         // A custom editor was found. If not already wrapped with a CellEditor, wrap it, and stash it back
114806         // If it's not even a Field, just a config object, instantiate it before wrapping it.
114807         if (editor) {
114808             if (!(editor instanceof Ext.grid.CellEditor)) {
114809                 if (!(editor instanceof Ext.form.field.Base)) {
114810                     editor = Ext.ComponentManager.create(editor, 'textfield');
114811                 }
114812                 editor = me.customEditors[propName] = Ext.create('Ext.grid.CellEditor', { field: editor });
114813             }
114814         } else if (Ext.isDate(val)) {
114815             editor = me.editors.date;
114816         } else if (Ext.isNumber(val)) {
114817             editor = me.editors.number;
114818         } else if (Ext.isBoolean(val)) {
114819             editor = me.editors['boolean'];
114820         } else {
114821             editor = me.editors.string;
114822         }
114823
114824         // Give the editor a unique ID because the CellEditing plugin caches them
114825         editor.editorId = propName;
114826         return editor;
114827     },
114828
114829     beforeDestroy: function() {
114830         var me = this;
114831         me.callParent();
114832         me.destroyEditors(me.editors);
114833         me.destroyEditors(me.customEditors);
114834         delete me.source;
114835     },
114836
114837     destroyEditors: function (editors) {
114838         for (var ed in editors) {
114839             Ext.destroy(editors[ed]);
114840         }
114841     },
114842
114843     /**
114844      * Sets the source data object containing the property data.  The data object can contain one or more name/value
114845      * pairs representing all of the properties of an object to display in the grid, and this data will automatically
114846      * be loaded into the grid's {@link #store}.  The values should be supplied in the proper data type if needed,
114847      * otherwise string type will be assumed.  If the grid already contains data, this method will replace any
114848      * existing data.  See also the {@link #source} config value.  Example usage:
114849      * <pre><code>
114850 grid.setSource({
114851     "(name)": "My Object",
114852     "Created": Ext.Date.parse('10/15/2006', 'm/d/Y'),  // date type
114853     "Available": false,  // boolean type
114854     "Version": .01,      // decimal type
114855     "Description": "A test object"
114856 });
114857 </code></pre>
114858      * @param {Object} source The data object
114859      */
114860     setSource: function(source) {
114861         this.source = source;
114862         this.propStore.setSource(source);
114863     },
114864
114865     /**
114866      * Gets the source data object containing the property data.  See {@link #setSource} for details regarding the
114867      * format of the data object.
114868      * @return {Object} The data object
114869      */
114870     getSource: function() {
114871         return this.propStore.getSource();
114872     },
114873
114874     /**
114875      * Sets the value of a property.
114876      * @param {String} prop The name of the property to set
114877      * @param {Mixed} value The value to test
114878      * @param {Boolean} create (Optional) True to create the property if it doesn't already exist. Defaults to <tt>false</tt>.
114879      */
114880     setProperty: function(prop, value, create) {
114881         this.propStore.setValue(prop, value, create);
114882     },
114883
114884     /**
114885      * Removes a property from the grid.
114886      * @param {String} prop The name of the property to remove
114887      */
114888     removeProperty: function(prop) {
114889         this.propStore.remove(prop);
114890     }
114891
114892     /**
114893      * @cfg store
114894      * @hide
114895      */
114896     /**
114897      * @cfg colModel
114898      * @hide
114899      */
114900     /**
114901      * @cfg cm
114902      * @hide
114903      */
114904     /**
114905      * @cfg columns
114906      * @hide
114907      */
114908 });
114909 /**
114910  * @class Ext.grid.property.HeaderContainer
114911  * @extends Ext.grid.header.Container
114912  * A custom HeaderContainer for the {@link Ext.grid.property.Grid}.  Generally it should not need to be used directly.
114913  * @constructor
114914  * @param {Ext.grid.property.Grid} grid The grid this store will be bound to
114915  * @param {Object} source The source data config object
114916  */
114917 Ext.define('Ext.grid.property.HeaderContainer', {
114918
114919     extend: 'Ext.grid.header.Container',
114920
114921     alternateClassName: 'Ext.grid.PropertyColumnModel',
114922
114923     // private - strings used for locale support
114924     nameText : 'Name',
114925     valueText : 'Value',
114926     dateFormat : 'm/j/Y',
114927     trueText: 'true',
114928     falseText: 'false',
114929
114930     // private
114931     nameColumnCls: Ext.baseCSSPrefix + 'grid-property-name',
114932     
114933     constructor : function(grid, store) {
114934
114935         this.grid = grid;
114936         this.store = store;
114937         this.callParent([{
114938             items: [{
114939                 header: this.nameText,
114940                 width: 115,
114941                 sortable: true,
114942                 dataIndex: grid.nameField,
114943                 renderer: Ext.Function.bind(this.renderProp, this),
114944                 itemId: grid.nameField,
114945                 menuDisabled :true,
114946                 tdCls: this.nameColumnCls
114947             }, {
114948                 header: this.valueText,
114949                 renderer: Ext.Function.bind(this.renderCell, this),
114950                 getEditor: function(record) {
114951                     return grid.getCellEditor(record, this);
114952                 },
114953                 flex: 1,
114954                 fixed: true,
114955                 dataIndex: grid.valueField,
114956                 itemId: grid.valueField,
114957                 menuDisabled: true
114958             }]
114959         }]);
114960     },
114961
114962     // private
114963     // Render a property name cell
114964     renderProp : function(v) {
114965         return this.getPropertyName(v);
114966     },
114967
114968     // private
114969     // Render a property value cell
114970     renderCell : function(val, meta, rec) {
114971         var me = this,
114972             renderer = this.grid.customRenderers[rec.get(me.grid.nameField)],
114973             result = val;
114974
114975         if (renderer) {
114976             return renderer.apply(this, arguments);
114977         }
114978         if (Ext.isDate(val)) {
114979             result = this.renderDate(val);
114980         } else if (Ext.isBoolean(val)) {
114981             result = this.renderBool(val);
114982         }
114983         return Ext.util.Format.htmlEncode(result);
114984     },
114985
114986     // private
114987     renderDate : Ext.util.Format.date,
114988
114989     // private
114990     renderBool : function(bVal) {
114991         return this[bVal ? 'trueText' : 'falseText'];
114992     },
114993
114994     // private
114995     // Renders custom property names instead of raw names if defined in the Grid
114996     getPropertyName : function(name) {
114997         var pn = this.grid.propertyNames;
114998         return pn && pn[name] ? pn[name] : name;
114999     }
115000 });
115001 /**
115002  * @class Ext.grid.property.Property
115003  * A specific {@link Ext.data.Model} type that represents a name/value pair and is made to work with the
115004  * {@link Ext.grid.property.Grid}.  Typically, Properties do not need to be created directly as they can be
115005  * created implicitly by simply using the appropriate data configs either via the {@link Ext.grid.property.Grid#source}
115006  * config property or by calling {@link Ext.grid.property.Grid#setSource}.  However, if the need arises, these records
115007  * can also be created explicitly as shown below.  Example usage:
115008  * <pre><code>
115009 var rec = new Ext.grid.property.Property({
115010     name: 'birthday',
115011     value: Ext.Date.parse('17/06/1962', 'd/m/Y')
115012 });
115013 // Add record to an already populated grid
115014 grid.store.addSorted(rec);
115015 </code></pre>
115016  * @constructor
115017  * @param {Object} config A data object in the format:<pre><code>
115018 {
115019     name: [name],
115020     value: [value]
115021 }</code></pre>
115022  * The specified value's type
115023  * will be read automatically by the grid to determine the type of editor to use when displaying it.
115024  */
115025 Ext.define('Ext.grid.property.Property', {
115026     extend: 'Ext.data.Model',
115027
115028     alternateClassName: 'Ext.PropGridProperty',
115029
115030     fields: [{
115031         name: 'name',
115032         type: 'string'
115033     }, {
115034         name: 'value'
115035     }],
115036     idProperty: 'name'
115037 });
115038 /**
115039  * @class Ext.grid.property.Store
115040  * @extends Ext.data.Store
115041  * A custom {@link Ext.data.Store} for the {@link Ext.grid.property.Grid}. This class handles the mapping
115042  * between the custom data source objects supported by the grid and the {@link Ext.grid.property.Property} format
115043  * used by the {@link Ext.data.Store} base class.
115044  * @constructor
115045  * @param {Ext.grid.Grid} grid The grid this store will be bound to
115046  * @param {Object} source The source data config object
115047  */
115048 Ext.define('Ext.grid.property.Store', {
115049
115050     extend: 'Ext.data.Store',
115051
115052     alternateClassName: 'Ext.grid.PropertyStore',
115053
115054     uses: ['Ext.data.reader.Reader', 'Ext.data.proxy.Proxy', 'Ext.data.ResultSet', 'Ext.grid.property.Property'],
115055
115056     constructor : function(grid, source){
115057         this.grid = grid;
115058         this.source = source;
115059         this.callParent([{
115060             data: source,
115061             model: Ext.grid.property.Property,
115062             proxy: this.getProxy()
115063         }]);
115064     },
115065
115066     // Return a singleton, customized Proxy object which configures itself with a custom Reader
115067     getProxy: function() {
115068         if (!this.proxy) {
115069             Ext.grid.property.Store.prototype.proxy = Ext.create('Ext.data.proxy.Memory', {
115070                 model: Ext.grid.property.Property,
115071                 reader: this.getReader()
115072             });
115073         }
115074         return this.proxy;
115075     },
115076
115077     // Return a singleton, customized Reader object which reads Ext.grid.property.Property records from an object.
115078     getReader: function() {
115079         if (!this.reader) {
115080             Ext.grid.property.Store.prototype.reader = Ext.create('Ext.data.reader.Reader', {
115081                 model: Ext.grid.property.Property,
115082
115083                 buildExtractors: Ext.emptyFn,
115084
115085                 read: function(dataObject) {
115086                     return this.readRecords(dataObject);
115087                 },
115088
115089                 readRecords: function(dataObject) {
115090                     var val,
115091                         result = {
115092                             records: [],
115093                             success: true
115094                         };
115095
115096                     for (var propName in dataObject) {
115097                         val = dataObject[propName];
115098                         if (dataObject.hasOwnProperty(propName) && this.isEditableValue(val)) {
115099                             result.records.push(new Ext.grid.property.Property({
115100                                 name: propName,
115101                                 value: val
115102                             }, propName));
115103                         }
115104                     }
115105                     result.total = result.count = result.records.length;
115106                     return Ext.create('Ext.data.ResultSet', result);
115107                 },
115108
115109                 // private
115110                 isEditableValue: function(val){
115111                     return Ext.isPrimitive(val) || Ext.isDate(val);
115112                 }
115113             });
115114         }
115115         return this.reader;
115116     },
115117
115118     // protected - should only be called by the grid.  Use grid.setSource instead.
115119     setSource : function(dataObject) {
115120         var me = this;
115121
115122         me.source = dataObject;
115123         me.suspendEvents();
115124         me.removeAll();
115125         me.proxy.data = dataObject;
115126         me.load();
115127         me.resumeEvents();
115128         me.fireEvent('datachanged', me);
115129     },
115130
115131     // private
115132     getProperty : function(row) {
115133        return Ext.isNumber(row) ? this.store.getAt(row) : this.store.getById(row);
115134     },
115135
115136     // private
115137     setValue : function(prop, value, create){
115138         var r = this.getRec(prop);
115139         if (r) {
115140             r.set('value', value);
115141             this.source[prop] = value;
115142         } else if (create) {
115143             // only create if specified.
115144             this.source[prop] = value;
115145             r = new Ext.grid.property.Property({name: prop, value: value}, prop);
115146             this.store.add(r);
115147         }
115148     },
115149
115150     // private
115151     remove : function(prop) {
115152         var r = this.getRec(prop);
115153         if(r) {
115154             this.store.remove(r);
115155             delete this.source[prop];
115156         }
115157     },
115158
115159     // private
115160     getRec : function(prop) {
115161         return this.store.getById(prop);
115162     },
115163
115164     // protected - should only be called by the grid.  Use grid.getSource instead.
115165     getSource : function() {
115166         return this.source;
115167     }
115168 });
115169 /**
115170  * Component layout for components which maintain an inner body element which must be resized to synchronize with the
115171  * Component size.
115172  * @class Ext.layout.component.Body
115173  * @extends Ext.layout.component.Component
115174  * @private
115175  */
115176
115177 Ext.define('Ext.layout.component.Body', {
115178
115179     /* Begin Definitions */
115180
115181     alias: ['layout.body'],
115182
115183     extend: 'Ext.layout.component.Component',
115184
115185     uses: ['Ext.layout.container.Container'],
115186
115187     /* End Definitions */
115188
115189     type: 'body',
115190     
115191     onLayout: function(width, height) {
115192         var me = this,
115193             owner = me.owner;
115194
115195         // Size the Component's encapsulating element according to the dimensions
115196         me.setTargetSize(width, height);
115197
115198         // Size the Component's body element according to the content box of the encapsulating element
115199         me.setBodySize.apply(me, arguments);
115200
115201         // We need to bind to the owner whenever we do not have a user set height or width.
115202         if (owner && owner.layout && owner.layout.isLayout) {
115203             if (!Ext.isNumber(owner.height) || !Ext.isNumber(owner.width)) {
115204                 owner.layout.bindToOwnerCtComponent = true;
115205             }
115206             else {
115207                 owner.layout.bindToOwnerCtComponent = false;
115208             }
115209         }
115210         
115211         me.callParent(arguments);
115212     },
115213
115214     /**
115215      * @private
115216      * <p>Sizes the Component's body element to fit exactly within the content box of the Component's encapsulating element.<p>
115217      */
115218     setBodySize: function(width, height) {
115219         var me = this,
115220             owner = me.owner,
115221             frameSize = owner.frameSize,
115222             isNumber = Ext.isNumber;
115223
115224         if (isNumber(width)) {
115225             width -= owner.el.getFrameWidth('lr') - frameSize.left - frameSize.right;
115226         }
115227         if (isNumber(height)) {
115228             height -= owner.el.getFrameWidth('tb') - frameSize.top - frameSize.bottom;
115229         }
115230
115231         me.setElementSize(owner.body, width, height);
115232     }
115233 });
115234 /**
115235  * Component layout for Ext.form.FieldSet components
115236  * @class Ext.layout.component.FieldSet
115237  * @extends Ext.layout.component.Body
115238  * @private
115239  */
115240 Ext.define('Ext.layout.component.FieldSet', {
115241     extend: 'Ext.layout.component.Body',
115242     alias: ['layout.fieldset'],
115243
115244     type: 'fieldset',
115245
115246     doContainerLayout: function() {
115247         // Prevent layout/rendering of children if the fieldset is collapsed
115248         if (!this.owner.collapsed) {
115249             this.callParent();
115250         }
115251     }
115252 });
115253 /**
115254  * Component layout for tabs
115255  * @class Ext.layout.component.Tab
115256  * @extends Ext.layout.component.Button
115257  * @private
115258  */
115259 Ext.define('Ext.layout.component.Tab', {
115260
115261     alias: ['layout.tab'],
115262
115263     extend: 'Ext.layout.component.Button',
115264
115265     //type: 'button',
115266
115267     beforeLayout: function() {
115268         var me = this, dirty = me.lastClosable !== me.owner.closable;
115269
115270         if (dirty) {
115271             delete me.adjWidth;
115272         }
115273
115274         return this.callParent(arguments) || dirty;
115275     },
115276
115277     onLayout: function () {
115278         var me = this;
115279
115280         me.callParent(arguments);
115281
115282         me.lastClosable = me.owner.closable;
115283     }
115284 });
115285 /**
115286  * @private
115287  * @class Ext.layout.component.field.File
115288  * @extends Ext.layout.component.field.Field
115289  * Layout class for {@link Ext.form.field.File} fields. Adjusts the input field size to accommodate
115290  * the file picker trigger button.
115291  * @private
115292  */
115293
115294 Ext.define('Ext.layout.component.field.File', {
115295     alias: ['layout.filefield'],
115296     extend: 'Ext.layout.component.field.Field',
115297
115298     type: 'filefield',
115299
115300     sizeBodyContents: function(width, height) {
115301         var me = this,
115302             owner = me.owner;
115303
115304         if (!owner.buttonOnly) {
115305             // Decrease the field's width by the width of the button and the configured buttonMargin.
115306             // Both the text field and the button are floated left in CSS so they'll stack up side by side.
115307             me.setElementSize(owner.inputEl, Ext.isNumber(width) ? width - owner.button.getWidth() - owner.buttonMargin : width);
115308         }
115309     }
115310 });
115311 /**
115312  * @class Ext.layout.component.field.Slider
115313  * @extends Ext.layout.component.field.Field
115314  * @private
115315  */
115316
115317 Ext.define('Ext.layout.component.field.Slider', {
115318
115319     /* Begin Definitions */
115320
115321     alias: ['layout.sliderfield'],
115322
115323     extend: 'Ext.layout.component.field.Field',
115324
115325     /* End Definitions */
115326
115327     type: 'sliderfield',
115328
115329     sizeBodyContents: function(width, height) {
115330         var owner = this.owner,
115331             thumbs = owner.thumbs,
115332             length = thumbs.length,
115333             inputEl = owner.inputEl,
115334             innerEl = owner.innerEl,
115335             endEl = owner.endEl,
115336             i = 0;
115337
115338         /*
115339          * If we happen to be animating during a resize, the position of the thumb will likely be off
115340          * when the animation stops. As such, just stop any animations before syncing the thumbs.
115341          */
115342         for(; i < length; ++i) {
115343             thumbs[i].el.stopAnimation();
115344         }
115345         
115346         if (owner.vertical) {
115347             inputEl.setHeight(height);
115348             innerEl.setHeight(Ext.isNumber(height) ? height - inputEl.getPadding('t') - endEl.getPadding('b') : height);
115349         }
115350         else {
115351             inputEl.setWidth(width);
115352             innerEl.setWidth(Ext.isNumber(width) ? width - inputEl.getPadding('l') - endEl.getPadding('r') : width);
115353         }
115354         owner.syncThumbs();
115355     }
115356 });
115357
115358 /**
115359  * @class Ext.layout.container.Absolute
115360  * @extends Ext.layout.container.Anchor
115361  * <p>This is a layout that inherits the anchoring of <b>{@link Ext.layout.container.Anchor}</b> and adds the
115362  * ability for x/y positioning using the standard x and y component config options.</p>
115363  * <p>This class is intended to be extended or created via the <tt><b>{@link Ext.container.Container#layout layout}</b></tt>
115364  * configuration property.  See <tt><b>{@link Ext.container.Container#layout}</b></tt> for additional details.</p>
115365  * {@img Ext.layout.container.Absolute/Ext.layout.container.Absolute.png Ext.layout.container.Absolute container layout}
115366  * <p>Example usage:</p>
115367  * <pre><code>
115368     Ext.create('Ext.form.Panel', {
115369         title: 'Absolute Layout',
115370         width: 300,
115371         height: 275,
115372         layout:'absolute',
115373         layoutConfig: {
115374             // layout-specific configs go here
115375             //itemCls: 'x-abs-layout-item',
115376         },
115377         url:'save-form.php',
115378         defaultType: 'textfield',
115379         items: [{
115380             x: 10,
115381             y: 10,
115382             xtype:'label',
115383             text: 'Send To:'
115384         },{
115385             x: 80,
115386             y: 10,
115387             name: 'to',
115388             anchor:'90%'  // anchor width by percentage
115389         },{
115390             x: 10,
115391             y: 40,
115392             xtype:'label',
115393             text: 'Subject:'
115394         },{
115395             x: 80,
115396             y: 40,
115397             name: 'subject',
115398             anchor: '90%'  // anchor width by percentage
115399         },{
115400             x:0,
115401             y: 80,
115402             xtype: 'textareafield',
115403             name: 'msg',
115404             anchor: '100% 100%'  // anchor width and height
115405         }],
115406         renderTo: Ext.getBody()
115407     });
115408 </code></pre>
115409  */
115410
115411 Ext.define('Ext.layout.container.Absolute', {
115412
115413     /* Begin Definitions */
115414
115415     alias: 'layout.absolute',
115416     extend: 'Ext.layout.container.Anchor',
115417     requires: ['Ext.chart.axis.Axis', 'Ext.fx.Anim'],
115418     alternateClassName: 'Ext.layout.AbsoluteLayout',
115419
115420     /* End Definitions */
115421
115422     itemCls: Ext.baseCSSPrefix + 'abs-layout-item',
115423
115424     type: 'absolute',
115425
115426     onLayout: function() {
115427         var me = this,
115428             target = me.getTarget(),
115429             targetIsBody = target.dom === document.body;
115430
115431         // Do not set position: relative; when the absolute layout target is the body
115432         if (!targetIsBody) {
115433             target.position();
115434         }
115435         me.paddingLeft = target.getPadding('l');
115436         me.paddingTop = target.getPadding('t');
115437         me.callParent(arguments);
115438     },
115439
115440     // private
115441     adjustWidthAnchor: function(value, comp) {
115442         //return value ? value - comp.getPosition(true)[0] + this.paddingLeft: value;
115443         return value ? value - comp.getPosition(true)[0] : value;
115444     },
115445
115446     // private
115447     adjustHeightAnchor: function(value, comp) {
115448         //return value ? value - comp.getPosition(true)[1] + this.paddingTop: value;
115449         return value ? value - comp.getPosition(true)[1] : value;
115450     }
115451 });
115452 /**
115453  * @class Ext.layout.container.Accordion
115454  * @extends Ext.layout.container.VBox
115455  * <p>This is a layout that manages multiple Panels in an expandable accordion style such that only
115456  * <b>one Panel can be expanded at any given time</b>. Each Panel has built-in support for expanding and collapsing.</p>
115457  * <p>Note: Only Ext.Panels <b>and all subclasses of Ext.panel.Panel</b> may be used in an accordion layout Container.</p>
115458  * {@img Ext.layout.container.Accordion/Ext.layout.container.Accordion.png Ext.layout.container.Accordion container layout}
115459  * <p>Example usage:</p>
115460  * <pre><code>
115461     Ext.create('Ext.panel.Panel', {
115462         title: 'Accordion Layout',
115463         width: 300,
115464         height: 300,
115465         layout:'accordion',
115466         defaults: {
115467             // applied to each contained panel
115468             bodyStyle: 'padding:15px'
115469         },
115470         layoutConfig: {
115471             // layout-specific configs go here
115472             titleCollapse: false,
115473             animate: true,
115474             activeOnTop: true
115475         },
115476         items: [{
115477             title: 'Panel 1',
115478             html: '<p>Panel content!</p>'
115479         },{
115480             title: 'Panel 2',
115481             html: '<p>Panel content!</p>'
115482         },{
115483             title: 'Panel 3',
115484             html: '<p>Panel content!</p>'
115485         }],
115486         renderTo: Ext.getBody()
115487     });
115488 </code></pre>
115489  */
115490 Ext.define('Ext.layout.container.Accordion', {
115491     extend: 'Ext.layout.container.VBox',
115492     alias: ['layout.accordion'],
115493     alternateClassName: 'Ext.layout.AccordionLayout',
115494     
115495     align: 'stretch',
115496
115497     /**
115498      * @cfg {Boolean} fill
115499      * True to adjust the active item's height to fill the available space in the container, false to use the
115500      * item's current height, or auto height if not explicitly set (defaults to true).
115501      */
115502     fill : true,
115503     /**
115504      * @cfg {Boolean} autoWidth
115505      * <p><b>This config is ignored in ExtJS 4.x.</b></p>
115506      * Child Panels have their width actively managed to fit within the accordion's width.
115507      */
115508     autoWidth : true,
115509     /**
115510      * @cfg {Boolean} titleCollapse
115511      * <p><b>Not implemented in PR2.</b></p>
115512      * True to allow expand/collapse of each contained panel by clicking anywhere on the title bar, false to allow
115513      * expand/collapse only when the toggle tool button is clicked (defaults to true).  When set to false,
115514      * {@link #hideCollapseTool} should be false also.
115515      */
115516     titleCollapse : true,
115517     /**
115518      * @cfg {Boolean} hideCollapseTool
115519      * True to hide the contained Panels' collapse/expand toggle buttons, false to display them (defaults to false).
115520      * When set to true, {@link #titleCollapse} is automatically set to <code>true</code>.
115521      */
115522     hideCollapseTool : false,
115523     /**
115524      * @cfg {Boolean} collapseFirst
115525      * True to make sure the collapse/expand toggle button always renders first (to the left of) any other tools
115526      * in the contained Panels' title bars, false to render it last (defaults to false).
115527      */
115528     collapseFirst : false,
115529     /**
115530      * @cfg {Boolean} animate
115531      * True to slide the contained panels open and closed during expand/collapse using animation, false to open and
115532      * close directly with no animation (defaults to <code>true</code>). Note: The layout performs animated collapsing
115533      * and expanding, <i>not</i> the child Panels.
115534      */
115535     animate : true,
115536     /**
115537      * @cfg {Boolean} activeOnTop
115538      * <p><b>Not implemented in PR4.</b></p>
115539      * <p>Only valid when {@link #multi" is <code>false</code>.</p>
115540      * True to swap the position of each panel as it is expanded so that it becomes the first item in the container,
115541      * false to keep the panels in the rendered order. <b>This is NOT compatible with "animate:true"</b> (defaults to false).
115542      */
115543     activeOnTop : false,
115544     /**
115545      * @cfg {Boolean} multi
115546      * Defaults to <code>false</code>. Set to <code>true</code> to enable multiple accordion items to be open at once.
115547      */
115548     multi: false,
115549
115550     constructor: function() {
115551         var me = this;
115552
115553         me.callParent(arguments);
115554
115555         // animate flag must be false during initial render phase so we don't get animations.
115556         me.initialAnimate = me.animate;
115557         me.animate = false;
115558
115559         // Child Panels are not absolutely positioned if we are not filling, so use a different itemCls.
115560         if (me.fill === false) {
115561             me.itemCls = Ext.baseCSSPrefix + 'accordion-item';
115562         }
115563     },
115564
115565     // Cannot lay out a fitting accordion before we have been allocated a height.
115566     // So during render phase, layout will not be performed.
115567     beforeLayout: function() {
115568         var me = this;
115569
115570         me.callParent(arguments);
115571         if (me.fill) {
115572             if (!me.owner.el.dom.style.height) {
115573                 return false;
115574             }
115575         } else {
115576             me.owner.componentLayout.monitorChildren = false;
115577             me.autoSize = true;
115578             me.owner.setAutoScroll(true);
115579         }
115580     },
115581
115582     renderItems : function(items, target) {
115583         var me = this,
115584             ln = items.length,
115585             i = 0,
115586             comp,
115587             targetSize = me.getLayoutTargetSize(),
115588             renderedPanels = [],
115589             border;
115590
115591         for (; i < ln; i++) {
115592             comp = items[i];
115593             if (!comp.rendered) {
115594                 renderedPanels.push(comp);
115595
115596                 // Set up initial properties for Panels in an accordion.
115597                 if (me.collapseFirst) {
115598                     comp.collapseFirst = me.collapseFirst;
115599                 }
115600                 if (me.hideCollapseTool) {
115601                     comp.hideCollapseTool = me.hideCollapseTool;
115602                     comp.titleCollapse = true;
115603                 }
115604                 else if (me.titleCollapse) {
115605                     comp.titleCollapse = me.titleCollapse;
115606                 }
115607
115608                 delete comp.hideHeader;
115609                 comp.collapsible = true;
115610                 comp.title = comp.title || '&#160;';
115611                 comp.setBorder(false);
115612
115613                 // Set initial sizes
115614                 comp.width = targetSize.width;
115615                 if (me.fill) {
115616                     delete comp.height;
115617                     delete comp.flex;
115618
115619                     // If there is an expanded item, all others must be rendered collapsed.
115620                     if (me.expandedItem !== undefined) {
115621                         comp.collapsed = true;
115622                     }
115623                     // Otherwise expand the first item with collapsed explicitly configured as false
115624                     else if (comp.collapsed === false) {
115625                         comp.flex = 1;
115626                         me.expandedItem = i;
115627                     } else {
115628                         comp.collapsed = true;
115629                     }
115630                 } else {
115631                     delete comp.flex;
115632                     comp.animCollapse = me.initialAnimate;
115633                     comp.autoHeight = true;
115634                     comp.autoScroll = false;
115635                 }
115636             }
115637         }
115638
115639         // If no collapsed:false Panels found, make the first one expanded.
115640         if (ln && me.expandedItem === undefined) {
115641             me.expandedItem = 0;
115642             comp = items[0];
115643             comp.collapsed = false;
115644             if (me.fill) {
115645                 comp.flex = 1;
115646             }
115647         }
115648         
115649         // Render all Panels.
115650         me.callParent(arguments);
115651                 
115652         // Postprocess rendered Panels.
115653         ln = renderedPanels.length;
115654         for (i = 0; i < ln; i++) {
115655             comp = renderedPanels[i];
115656
115657             // Delete the dimension property so that our align: 'stretch' processing manages the width from here
115658             delete comp.width;
115659
115660             comp.header.addCls(Ext.baseCSSPrefix + 'accordion-hd');
115661             comp.body.addCls(Ext.baseCSSPrefix + 'accordion-body');
115662             
115663             // If we are fitting, then intercept expand/collapse requests. 
115664             if (me.fill) {
115665                 me.owner.mon(comp, {
115666                     show: me.onComponentShow,
115667                     beforeexpand: me.onComponentExpand,
115668                     beforecollapse: me.onComponentCollapse,
115669                     scope: me
115670                 });
115671             }
115672         }
115673     },
115674
115675     onLayout: function() {
115676         var me = this;
115677         
115678         me.updatePanelClasses();
115679                 
115680         if (me.fill) {
115681             me.callParent(arguments);
115682         } else {
115683             var targetSize = me.getLayoutTargetSize(),
115684                 items = me.getVisibleItems(),
115685                 len = items.length,
115686                 i = 0, comp;
115687
115688             for (; i < len; i++) {
115689                 comp = items[i];
115690                 if (comp.collapsed) {
115691                     items[i].setWidth(targetSize.width);
115692                 } else {
115693                     items[i].setSize(null, null);
115694                 }
115695             }
115696         }
115697         
115698         return me;
115699     },
115700     
115701     updatePanelClasses: function() {
115702         var children = this.getLayoutItems(),
115703             ln = children.length,
115704             siblingCollapsed = true,
115705             i, child;
115706             
115707         for (i = 0; i < ln; i++) {
115708             child = children[i];
115709             if (!siblingCollapsed) {
115710                 child.header.addCls(Ext.baseCSSPrefix + 'accordion-hd-sibling-expanded');
115711             }
115712             else {
115713                 child.header.removeCls(Ext.baseCSSPrefix + 'accordion-hd-sibling-expanded');
115714             }
115715             if (i + 1 == ln && child.collapsed) {
115716                 child.header.addCls(Ext.baseCSSPrefix + 'accordion-hd-last-collapsed');
115717             }
115718             else {
115719                 child.header.removeCls(Ext.baseCSSPrefix + 'accordion-hd-last-collapsed');
115720             }
115721             siblingCollapsed = child.collapsed;
115722         }
115723     },
115724
115725     // When a Component expands, adjust the heights of the other Components to be just enough to accommodate
115726     // their headers.
115727     // The expanded Component receives the only flex value, and so gets all remaining space.
115728     onComponentExpand: function(toExpand) {
115729         var me = this,
115730             it = me.owner.items.items,
115731             len = it.length,
115732             i = 0,
115733             comp;
115734
115735         for (; i < len; i++) {
115736             comp = it[i];
115737             if (comp === toExpand && comp.collapsed) {
115738                 me.setExpanded(comp);
115739             } else if (!me.multi && (comp.rendered && comp.header.rendered && comp !== toExpand && !comp.collapsed)) {
115740                 me.setCollapsed(comp);
115741             }
115742         }
115743         
115744         me.animate = me.initialAnimate;
115745         me.layout();
115746         me.animate = false;
115747         return false;
115748     },
115749
115750     onComponentCollapse: function(comp) {
115751         var me = this,
115752             toExpand = comp.next() || comp.prev(),
115753             expanded = me.multi ? me.owner.query('>panel:not([collapsed])') : [];
115754
115755         // If we are allowing multi, and the "toCollapse" component is NOT the only expanded Component,
115756         // then ask the box layout to collapse it to its header.
115757         if (me.multi) {
115758             me.setCollapsed(comp);
115759
115760             // If the collapsing Panel is the only expanded one, expand the following Component.
115761             // All this is handling fill: true, so there must be at least one expanded,
115762             if (expanded.length === 1 && expanded[0] === comp) {
115763                 me.setExpanded(toExpand);
115764             }
115765             
115766             me.animate = me.initialAnimate;
115767             me.layout();
115768             me.animate = false;
115769         }
115770         // Not allowing multi: expand the next sibling if possible, prev sibling if we collapsed the last
115771         else if (toExpand) {
115772             me.onComponentExpand(toExpand);
115773         }
115774         return false;
115775     },
115776
115777     onComponentShow: function(comp) {
115778         // Showing a Component means that you want to see it, so expand it.
115779         this.onComponentExpand(comp);
115780     },
115781
115782     setCollapsed: function(comp) {
115783         var otherDocks = comp.getDockedItems(),
115784             dockItem,
115785             len = otherDocks.length,
115786             i = 0;
115787
115788         // Hide all docked items except the header
115789         comp.hiddenDocked = [];
115790         for (; i < len; i++) {
115791             dockItem = otherDocks[i];
115792             if ((dockItem !== comp.header) && !dockItem.hidden) {
115793                 dockItem.hidden = true;
115794                 comp.hiddenDocked.push(dockItem);
115795             }
115796         }
115797         comp.addCls(comp.collapsedCls);
115798         comp.header.addCls(comp.collapsedHeaderCls);
115799         comp.height = comp.header.getHeight();
115800         comp.el.setHeight(comp.height);
115801         comp.collapsed = true;
115802         delete comp.flex;
115803         comp.fireEvent('collapse', comp);
115804         if (comp.collapseTool) {
115805             comp.collapseTool.setType('expand-' + comp.getOppositeDirection(comp.collapseDirection));
115806         }
115807     },
115808
115809     setExpanded: function(comp) {
115810         var otherDocks = comp.hiddenDocked,
115811             len = otherDocks ? otherDocks.length : 0,
115812             i = 0;
115813
115814         // Show temporarily hidden docked items
115815         for (; i < len; i++) {
115816             otherDocks[i].hidden = false;
115817         }
115818
115819         // If it was an initial native collapse which hides the body
115820         if (!comp.body.isVisible()) {
115821             comp.body.show();
115822         }
115823         delete comp.collapsed;
115824         delete comp.height;
115825         delete comp.componentLayout.lastComponentSize;
115826         comp.suspendLayout = false;
115827         comp.flex = 1;
115828         comp.removeCls(comp.collapsedCls);
115829         comp.header.removeCls(comp.collapsedHeaderCls);
115830         comp.fireEvent('expand', comp);
115831         if (comp.collapseTool) {
115832             comp.collapseTool.setType('collapse-' + comp.collapseDirection);
115833         }
115834         comp.setAutoScroll(comp.initialConfig.autoScroll);
115835     }
115836 });
115837 /**
115838  * @class Ext.resizer.Splitter
115839  * @extends Ext.Component
115840  * <p>This class functions <b>between siblings of a {@link Ext.layout.container.VBox VBox} or {@link Ext.layout.container.HBox HBox}
115841  * layout</b> to resize both immediate siblings.</p>
115842  * <p>By default it will set the size of both siblings. <b>One</b> of the siblings may be configured with
115843  * <code>{@link Ext.Component#maintainFlex maintainFlex}: true</code> which will cause it not to receive a new size explicitly, but to be resized
115844  * by the layout.</p>
115845  * <p>A Splitter may be configured to show a centered mini-collapse tool orientated to collapse the {@link #collapseTarget}.
115846  * The Splitter will then call that sibling Panel's {@link Ext.panel.Panel#collapse collapse} or {@link Ext.panel.Panel#expand expand} method
115847  * to perform the appropriate operation (depending on the sibling collapse state). To create the mini-collapse tool but take care
115848  * of collapsing yourself, configure the splitter with <code>{@link #performCollapse} false</code>.</p>
115849  *
115850  * @xtype splitter
115851  */
115852 Ext.define('Ext.resizer.Splitter', {
115853     extend: 'Ext.Component',
115854     requires: ['Ext.XTemplate'],
115855     uses: ['Ext.resizer.SplitterTracker'],
115856     alias: 'widget.splitter',
115857
115858     renderTpl: [
115859         '<tpl if="collapsible===true"><div class="' + Ext.baseCSSPrefix + 'collapse-el ' + Ext.baseCSSPrefix + 'layout-split-{collapseDir}">&nbsp;</div></tpl>'
115860     ],
115861
115862     baseCls: Ext.baseCSSPrefix + 'splitter',
115863     collapsedCls: Ext.baseCSSPrefix + 'splitter-collapsed',
115864
115865     /**
115866      * @cfg {Boolean} collapsible
115867      * <code>true</code> to show a mini-collapse tool in the Splitter to toggle expand and collapse on the {@link #collapseTarget} Panel.
115868      * Defaults to the {@link Ext.panel.Panel#collapsible collapsible} setting of the Panel.
115869      */
115870     collapsible: false,
115871
115872     /**
115873      * @cfg {Boolean} performCollapse
115874      * <p>Set to <code>false</code> to prevent this Splitter's mini-collapse tool from managing the collapse
115875      * state of the {@link #collapseTarget}.</p>
115876      */
115877
115878     /**
115879      * @cfg {Boolean} collapseOnDblClick
115880      * <code>true</code> to enable dblclick to toggle expand and collapse on the {@link #collapseTarget} Panel.
115881      */
115882     collapseOnDblClick: true,
115883
115884     /**
115885      * @cfg {Number} defaultSplitMin
115886      * Provides a default minimum width or height for the two components
115887      * that the splitter is between.
115888      */
115889     defaultSplitMin: 40,
115890
115891     /**
115892      * @cfg {Number} defaultSplitMax
115893      * Provides a default maximum width or height for the two components
115894      * that the splitter is between.
115895      */
115896     defaultSplitMax: 1000,
115897
115898     width: 5,
115899     height: 5,
115900
115901     /**
115902      * @cfg {Mixed} collapseTarget
115903      * <p>A string describing the relative position of the immediate sibling Panel to collapse. May be 'prev' or 'next' (Defaults to 'next')</p>
115904      * <p>Or the immediate sibling Panel to collapse.</p>
115905      * <p>The orientation of the mini-collapse tool will be inferred from this setting.</p>
115906      * <p><b>Note that only Panels may be collapsed.</b></p>
115907      */
115908     collapseTarget: 'next',
115909
115910     /**
115911      * @property orientation
115912      * @type String
115913      * Orientation of this Splitter. <code>'vertical'</code> when used in an hbox layout, <code>'horizontal'</code>
115914      * when used in a vbox layout.
115915      */
115916
115917     onRender: function() {
115918         var me = this,
115919             target = me.getCollapseTarget(),
115920             collapseDir = me.getCollapseDirection();
115921
115922         Ext.applyIf(me.renderData, {
115923             collapseDir: collapseDir,
115924             collapsible: me.collapsible || target.collapsible
115925         });
115926         Ext.applyIf(me.renderSelectors, {
115927             collapseEl: '.' + Ext.baseCSSPrefix + 'collapse-el'
115928         });
115929
115930         this.callParent(arguments);
115931
115932         // Add listeners on the mini-collapse tool unless performCollapse is set to false
115933         if (me.performCollapse !== false) {
115934             if (me.renderData.collapsible) {
115935                 me.mon(me.collapseEl, 'click', me.toggleTargetCmp, me);
115936             }
115937             if (me.collapseOnDblClick) {
115938                 me.mon(me.el, 'dblclick', me.toggleTargetCmp, me);
115939             }
115940         }
115941
115942         // Ensure the mini collapse icon is set to the correct direction when the target is collapsed/expanded by any means
115943         me.mon(target, 'collapse', me.onTargetCollapse, me);
115944         me.mon(target, 'expand', me.onTargetExpand, me);
115945
115946         me.el.addCls(me.baseCls + '-' + me.orientation);
115947         me.el.unselectable();
115948
115949         me.tracker = Ext.create('Ext.resizer.SplitterTracker', {
115950             el: me.el
115951         });
115952     },
115953
115954     getCollapseDirection: function() {
115955         var me = this,
115956             idx,
115957             type = me.ownerCt.layout.type;
115958
115959         // Avoid duplication of string tests.
115960         // Create a two bit truth table of the configuration of the Splitter:
115961         // Collapse Target | orientation
115962         //        0              0             = next, horizontal
115963         //        0              1             = next, vertical
115964         //        1              0             = prev, horizontal
115965         //        1              1             = prev, vertical
115966         if (me.collapseTarget.isComponent) {
115967             idx = Number(me.ownerCt.items.indexOf(me.collapseTarget) == me.ownerCt.items.indexOf(me) - 1) << 1 | Number(type == 'hbox');
115968         } else {
115969             idx = Number(me.collapseTarget == 'prev') << 1 | Number(type == 'hbox');
115970         }
115971
115972         // Read the data out the truth table
115973         me.orientation = ['horizontal', 'vertical'][idx & 1];
115974         return ['bottom', 'right', 'top', 'left'][idx];
115975     },
115976
115977     getCollapseTarget: function() {
115978         return this.collapseTarget.isComponent ? this.collapseTarget : this.collapseTarget == 'prev' ? this.previousSibling() : this.nextSibling();
115979     },
115980
115981     onTargetCollapse: function(target) {
115982         this.el.addCls(this.collapsedCls);
115983     },
115984
115985     onTargetExpand: function(target) {
115986         this.el.removeCls(this.collapsedCls);
115987     },
115988
115989     toggleTargetCmp: function(e, t) {
115990         var cmp = this.getCollapseTarget();
115991
115992         if (cmp.isVisible()) {
115993             // restore
115994             if (cmp.collapsed) {
115995                 cmp.expand(cmp.animCollapse);
115996             // collapse
115997             } else {
115998                 cmp.collapse(this.renderData.collapseDir, cmp.animCollapse);
115999             }
116000         }
116001     },
116002
116003     /*
116004      * Work around IE bug. %age margins do not get recalculated on element resize unless repaint called.
116005      */
116006     setSize: function() {
116007         var me = this;
116008         me.callParent(arguments);
116009         if (Ext.isIE) {
116010             me.el.repaint();
116011         }
116012     }
116013 });
116014
116015 /**
116016  * @class Ext.layout.container.Border
116017  * @extends Ext.layout.container.Container
116018  * <p>This is a multi-pane, application-oriented UI layout style that supports multiple
116019  * nested panels, automatic bars between regions and built-in
116020  * {@link Ext.panel.Panel#collapsible expanding and collapsing} of regions.</p>
116021  * <p>This class is intended to be extended or created via the <code>layout:'border'</code>
116022  * {@link Ext.container.Container#layout} config, and should generally not need to be created directly
116023  * via the new keyword.</p>
116024  * {@img Ext.layout.container.Border/Ext.layout.container.Border.png Ext.layout.container.Border container layout}
116025  * <p>Example usage:</p>
116026  * <pre><code>
116027      Ext.create('Ext.panel.Panel', {
116028         width: 500,
116029         height: 400,
116030         title: 'Border Layout',
116031         layout: 'border',
116032         items: [{
116033             title: 'South Region is resizable',
116034             region: 'south',     // position for region
116035             xtype: 'panel',
116036             height: 100,
116037             split: true,         // enable resizing
116038             margins: '0 5 5 5'
116039         },{
116040             // xtype: 'panel' implied by default
116041             title: 'West Region is collapsible',
116042             region:'west',
116043             xtype: 'panel',
116044             margins: '5 0 0 5',
116045             width: 200,
116046             collapsible: true,   // make collapsible
116047             id: 'west-region-container',
116048             layout: 'fit'
116049         },{
116050             title: 'Center Region',
116051             region: 'center',     // center region is required, no width/height specified
116052             xtype: 'panel',
116053             layout: 'fit',
116054             margins: '5 5 0 0'
116055         }],
116056         renderTo: Ext.getBody()
116057     });
116058 </code></pre>
116059  * <p><b><u>Notes</u></b>:</p><div class="mdetail-params"><ul>
116060  * <li>Any Container using the Border layout <b>must</b> have a child item with <code>region:'center'</code>.
116061  * The child item in the center region will always be resized to fill the remaining space not used by
116062  * the other regions in the layout.</li>
116063  * <li>Any child items with a region of <code>west</code> or <code>east</code> may be configured with either
116064  * 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>
116065  * <li>Any child items with a region of <code>north</code> or <code>south</code> may be configured with either
116066  * 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>
116067  * <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
116068  * Components within a BorderLayout, have them wrapped by an additional Container which is directly
116069  * managed by the BorderLayout.  If the region is to be collapsible, the Container used directly
116070  * by the BorderLayout manager should be a Panel.  In the following example a Container (an Ext.panel.Panel)
116071  * is added to the west region:<pre><code>
116072 wrc = {@link Ext#getCmp Ext.getCmp}('west-region-container');
116073 wrc.{@link Ext.container.Container#removeAll removeAll}();
116074 wrc.{@link Ext.container.Container#add add}({
116075     title: 'Added Panel',
116076     html: 'Some content'
116077 });
116078  * </code></pre>
116079  * </li>
116080  * <li><b>There is no BorderLayout.Region class in ExtJS 4.0+</b></li>
116081  * </ul></div>
116082  */
116083 Ext.define('Ext.layout.container.Border', {
116084
116085     alias: ['layout.border'],
116086     extend: 'Ext.layout.container.Container',
116087     requires: ['Ext.resizer.Splitter', 'Ext.container.Container', 'Ext.fx.Anim'],
116088     alternateClassName: 'Ext.layout.BorderLayout',
116089
116090     targetCls: Ext.baseCSSPrefix + 'border-layout-ct',
116091
116092     itemCls: Ext.baseCSSPrefix + 'border-item',
116093
116094     bindToOwnerCtContainer: true,
116095
116096     fixedLayout: false,
116097
116098     percentageRe: /(\d+)%/,
116099
116100     slideDirection: {
116101         north: 't',
116102         south: 'b',
116103         west: 'l',
116104         east: 'r'
116105     },
116106
116107     constructor: function(config) {
116108         this.initialConfig = config;
116109         this.callParent(arguments);
116110     },
116111
116112     onLayout: function() {
116113         var me = this;
116114         if (!me.borderLayoutInitialized) {
116115             me.initializeBorderLayout();
116116         }
116117
116118         // Delegate this operation to the shadow "V" or "H" box layout, and then down to any embedded layout.
116119         me.shadowLayout.onLayout();
116120         if (me.embeddedContainer) {
116121             me.embeddedContainer.layout.onLayout();
116122         }
116123
116124         // If the panel was originally configured with collapsed: true, it will have
116125         // been initialized with a "borderCollapse" flag: Collapse it now before the first layout.
116126         if (!me.initialCollapsedComplete) {
116127             Ext.iterate(me.regions, function(name, region){
116128                 if (region.borderCollapse) {
116129                     me.onBeforeRegionCollapse(region, region.collapseDirection, false, 0);
116130                 }
116131             });
116132             me.initialCollapsedComplete = true;
116133         }
116134     },
116135
116136     isValidParent : function(item, target, position) {
116137         if (!this.borderLayoutInitialized) {
116138             this.initializeBorderLayout();
116139         }
116140
116141         // Delegate this operation to the shadow "V" or "H" box layout.
116142         return this.shadowLayout.isValidParent(item, target, position);
116143     },
116144
116145     beforeLayout: function() {
116146         if (!this.borderLayoutInitialized) {
116147             this.initializeBorderLayout();
116148         }
116149
116150         // Delegate this operation to the shadow "V" or "H" box layout.
116151         this.shadowLayout.beforeLayout();
116152     },
116153
116154     renderItems: function(items, target) {
116155         Ext.Error.raise('This should not be called');
116156     },
116157
116158     renderItem: function(item) {
116159         Ext.Error.raise('This should not be called');
116160     },
116161
116162     initializeBorderLayout: function() {
116163         var me = this,
116164             i = 0,
116165             items = me.getLayoutItems(),
116166             ln = items.length,
116167             regions = (me.regions = {}),
116168             vBoxItems = [],
116169             hBoxItems = [],
116170             horizontalFlex = 0,
116171             verticalFlex = 0,
116172             comp, percentage;
116173
116174         // Map of Splitters for each region
116175         me.splitters = {};
116176
116177         // Map of regions
116178         for (; i < ln; i++) {
116179             comp = items[i];
116180             regions[comp.region] = comp;
116181
116182             // Intercept collapsing to implement showing an alternate Component as a collapsed placeholder
116183             if (comp.region != 'center' && comp.collapsible && comp.collapseMode != 'header') {
116184
116185                 // This layout intercepts any initial collapsed state. Panel must not do this itself.
116186                 comp.borderCollapse = comp.collapsed;
116187                 delete comp.collapsed;
116188
116189                 comp.on({
116190                     beforecollapse: me.onBeforeRegionCollapse,
116191                     beforeexpand: me.onBeforeRegionExpand,
116192                     destroy: me.onRegionDestroy,
116193                     scope: me
116194                 });
116195                 me.setupState(comp);
116196             }
116197         }
116198         if (!regions.center) {
116199             Ext.Error.raise("You must specify a center region when defining a BorderLayout.");
116200         }
116201         comp = regions.center;
116202         if (!comp.flex) {
116203             comp.flex = 1;
116204         }
116205         delete comp.width;
116206         comp.maintainFlex = true;
116207
116208         // Begin the VBox and HBox item list.
116209         comp = regions.west;
116210         if (comp) {
116211             comp.collapseDirection = Ext.Component.DIRECTION_LEFT;
116212             hBoxItems.push(comp);
116213             if (comp.split) {
116214                 hBoxItems.push(me.splitters.west = me.createSplitter(comp));
116215             }
116216             percentage = Ext.isString(comp.width) && comp.width.match(me.percentageRe);
116217             if (percentage) {
116218                 horizontalFlex += (comp.flex = parseInt(percentage[1], 10) / 100);
116219                 delete comp.width;
116220             }
116221         }
116222         comp = regions.north;
116223         if (comp) {
116224             comp.collapseDirection = Ext.Component.DIRECTION_TOP;
116225             vBoxItems.push(comp);
116226             if (comp.split) {
116227                 vBoxItems.push(me.splitters.north = me.createSplitter(comp));
116228             }
116229             percentage = Ext.isString(comp.height) && comp.height.match(me.percentageRe);
116230             if (percentage) {
116231                 verticalFlex += (comp.flex = parseInt(percentage[1], 10) / 100);
116232                 delete comp.height;
116233             }
116234         }
116235
116236         // Decide into which Collection the center region goes.
116237         if (regions.north || regions.south) {
116238             if (regions.east || regions.west) {
116239
116240                 // Create the embedded center. Mark it with the region: 'center' property so that it can be identified as the center.
116241                 vBoxItems.push(me.embeddedContainer = Ext.create('Ext.container.Container', {
116242                     xtype: 'container',
116243                     region: 'center',
116244                     id: me.owner.id + '-embedded-center',
116245                     cls: Ext.baseCSSPrefix + 'border-item',
116246                     flex: regions.center.flex,
116247                     maintainFlex: true,
116248                     layout: {
116249                         type: 'hbox',
116250                         align: 'stretch'
116251                     }
116252                 }));
116253                 hBoxItems.push(regions.center);
116254             }
116255             // No east or west: the original center goes straight into the vbox
116256             else {
116257                 vBoxItems.push(regions.center);
116258             }
116259         }
116260         // If we have no north or south, then the center is part of the HBox items
116261         else {
116262             hBoxItems.push(regions.center);
116263         }
116264
116265         // Finish off the VBox and HBox item list.
116266         comp = regions.south;
116267         if (comp) {
116268             comp.collapseDirection = Ext.Component.DIRECTION_BOTTOM;
116269             if (comp.split) {
116270                 vBoxItems.push(me.splitters.south = me.createSplitter(comp));
116271             }
116272             percentage = Ext.isString(comp.height) && comp.height.match(me.percentageRe);
116273             if (percentage) {
116274                 verticalFlex += (comp.flex = parseInt(percentage[1], 10) / 100);
116275                 delete comp.height;
116276             }
116277             vBoxItems.push(comp);
116278         }
116279         comp = regions.east;
116280         if (comp) {
116281             comp.collapseDirection = Ext.Component.DIRECTION_RIGHT;
116282             if (comp.split) {
116283                 hBoxItems.push(me.splitters.east = me.createSplitter(comp));
116284             }
116285             percentage = Ext.isString(comp.width) && comp.width.match(me.percentageRe);
116286             if (percentage) {
116287                 horizontalFlex += (comp.flex = parseInt(percentage[1], 10) / 100);
116288                 delete comp.width;
116289             }
116290             hBoxItems.push(comp);
116291         }
116292
116293         // Create the injected "items" collections for the Containers.
116294         // If we have north or south, then the shadow Container will be a VBox.
116295         // If there are also east or west regions, its center will be a shadow HBox.
116296         // If there are *only* east or west regions, then the shadow layout will be an HBox (or Fit).
116297         if (regions.north || regions.south) {
116298
116299             me.shadowContainer = Ext.create('Ext.container.Container', {
116300                 ownerCt: me.owner,
116301                 el: me.getTarget(),
116302                 layout: Ext.applyIf({
116303                     type: 'vbox',
116304                     align: 'stretch'
116305                 }, me.initialConfig)
116306             });
116307             me.createItems(me.shadowContainer, vBoxItems);
116308
116309             // Allow the Splitters to orientate themselves
116310             if (me.splitters.north) {
116311                 me.splitters.north.ownerCt = me.shadowContainer;
116312             }
116313             if (me.splitters.south) {
116314                 me.splitters.south.ownerCt = me.shadowContainer;
116315             }
116316
116317             // Inject items into the HBox Container if there is one - if there was an east or west.
116318             if (me.embeddedContainer) {
116319                 me.embeddedContainer.ownerCt = me.shadowContainer;
116320                 me.createItems(me.embeddedContainer, hBoxItems);
116321
116322                 // Allow the Splitters to orientate themselves
116323                 if (me.splitters.east) {
116324                     me.splitters.east.ownerCt = me.embeddedContainer;
116325                 }
116326                 if (me.splitters.west) {
116327                     me.splitters.west.ownerCt = me.embeddedContainer;
116328                 }
116329
116330                 // The east or west region wanted a percentage
116331                 if (horizontalFlex) {
116332                     regions.center.flex -= horizontalFlex;
116333                 }
116334                 // The north or south region wanted a percentage
116335                 if (verticalFlex) {
116336                     me.embeddedContainer.flex -= verticalFlex;
116337                 }
116338             } else {
116339                 // The north or south region wanted a percentage
116340                 if (verticalFlex) {
116341                     regions.center.flex -= verticalFlex;
116342                 }
116343             }
116344         }
116345         // If we have no north or south, then there's only one Container, and it's
116346         // an HBox, or, if only a center region was specified, a Fit.
116347         else {
116348             me.shadowContainer = Ext.create('Ext.container.Container', {
116349                 ownerCt: me.owner,
116350                 el: me.getTarget(),
116351                 layout: Ext.applyIf({
116352                     type: (hBoxItems.length == 1) ? 'fit' : 'hbox',
116353                     align: 'stretch'
116354                 }, me.initialConfig)
116355             });
116356             me.createItems(me.shadowContainer, hBoxItems);
116357
116358             // Allow the Splitters to orientate themselves
116359             if (me.splitters.east) {
116360                 me.splitters.east.ownerCt = me.shadowContainer;
116361             }
116362             if (me.splitters.west) {
116363                 me.splitters.west.ownerCt = me.shadowContainer;
116364             }
116365
116366             // The east or west region wanted a percentage
116367             if (horizontalFlex) {
116368                 regions.center.flex -= verticalFlex;
116369             }
116370         }
116371
116372         // Create upward links from the region Components to their shadow ownerCts
116373         for (i = 0, items = me.shadowContainer.items.items, ln = items.length; i < ln; i++) {
116374             items[i].shadowOwnerCt = me.shadowContainer;
116375         }
116376         if (me.embeddedContainer) {
116377             for (i = 0, items = me.embeddedContainer.items.items, ln = items.length; i < ln; i++) {
116378                 items[i].shadowOwnerCt = me.embeddedContainer;
116379             }
116380         }
116381
116382         // This is the layout that we delegate all operations to
116383         me.shadowLayout = me.shadowContainer.getLayout();
116384
116385         me.borderLayoutInitialized = true;
116386     },
116387
116388
116389     setupState: function(comp){
116390         var getState = comp.getState;
116391         comp.getState = function(){
116392             // call the original getState
116393             var state = getState.call(comp) || {},
116394                 region = comp.region;
116395
116396             state.collapsed = !!comp.collapsed;
116397             if (region == 'west' || region == 'east') {
116398                 state.width = comp.getWidth();
116399             } else {
116400                 state.height = comp.getHeight();
116401             }
116402             return state;
116403         };
116404         comp.addStateEvents(['collapse', 'expand', 'resize']);
116405     },
116406
116407     /**
116408      * Create the items collection for our shadow/embedded containers
116409      * @private
116410      */
116411     createItems: function(container, items){
116412         // Have to inject an items Collection *after* construction.
116413         // The child items of the shadow layout must retain their original, user-defined ownerCt
116414         delete container.items;
116415         container.initItems();
116416         container.items.addAll(items);
116417     },
116418
116419     // Private
116420     // Create a splitter for a child of the layout.
116421     createSplitter: function(comp) {
116422         var me = this,
116423             interceptCollapse = (comp.collapseMode != 'header'),
116424             resizer;
116425
116426         resizer = Ext.create('Ext.resizer.Splitter', {
116427             hidden: !!comp.hidden,
116428             collapseTarget: comp,
116429             performCollapse: !interceptCollapse,
116430             listeners: interceptCollapse ? {
116431                 click: {
116432                     fn: Ext.Function.bind(me.onSplitterCollapseClick, me, [comp]),
116433                     element: 'collapseEl'
116434                 }
116435             } : null
116436         });
116437
116438         // Mini collapse means that the splitter is the placeholder Component
116439         if (comp.collapseMode == 'mini') {
116440             comp.placeholder = resizer;
116441         }
116442
116443         // Arrange to hide/show a region's associated splitter when the region is hidden/shown
116444         comp.on({
116445             hide: me.onRegionVisibilityChange,
116446             show: me.onRegionVisibilityChange,
116447             scope: me
116448         });
116449         return resizer;
116450     },
116451
116452     // Hide/show a region's associated splitter when the region is hidden/shown
116453     onRegionVisibilityChange: function(comp){
116454         this.splitters[comp.region][comp.hidden ? 'hide' : 'show']();
116455         this.layout();
116456     },
116457
116458     // Called when a splitter mini-collapse tool is clicked on.
116459     // The listener is only added if this layout is controlling collapsing,
116460     // not if the component's collapseMode is 'mini' or 'header'.
116461     onSplitterCollapseClick: function(comp) {
116462         if (comp.collapsed) {
116463             this.onPlaceHolderToolClick(null, null, null, {client: comp});
116464         } else {
116465             comp.collapse();
116466         }
116467     },
116468
116469     /**
116470      * <p>Return the {@link Ext.panel.Panel#placeholder placeholder} Component to which the passed child Panel of the layout will collapse.
116471      * By default, this will be a {@link Ext.panel.Header Header} component (Docked to the appropriate border). See {@link Ext.panel.Panel#placeholder placeholder}.
116472      * config to customize this.</p>
116473      * <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>
116474      * @param {Panel} panel The child Panel of the layout for which to return the {@link Ext.panel.Panel#placeholder placeholder}.
116475      * @returns {Component} The Panel's {@link Ext.panel.Panel#placeholder placeholder} unless the {@link Ext.panel.Panel#collapseMode collapseMode} is
116476      * <code>'header'</code>, in which case <i>undefined</i> is returned.
116477      */
116478     getPlaceholder: function(comp) {
116479         var me = this,
116480             placeholder = comp.placeholder,
116481             shadowContainer = comp.shadowOwnerCt,
116482             shadowLayout = shadowContainer.layout,
116483             oppositeDirection = Ext.panel.Panel.prototype.getOppositeDirection(comp.collapseDirection),
116484             horiz = (comp.region == 'north' || comp.region == 'south');
116485
116486         // No placeholder if the collapse mode is not the Border layout default
116487         if (comp.collapseMode == 'header') {
116488             return;
116489         }
116490
116491         // Provide a replacement Container with an expand tool
116492         if (!placeholder) {
116493             if (comp.collapseMode == 'mini') {
116494                 placeholder = Ext.create('Ext.resizer.Splitter', {
116495                     id: 'collapse-placeholder-' + comp.id,
116496                     collapseTarget: comp,
116497                     performCollapse: false,
116498                     listeners: {
116499                         click: {
116500                             fn: Ext.Function.bind(me.onSplitterCollapseClick, me, [comp]),
116501                             element: 'collapseEl'
116502                         }
116503                     }
116504                 });
116505                 placeholder.addCls(placeholder.collapsedCls);
116506             } else {
116507                 placeholder = {
116508                     id: 'collapse-placeholder-' + comp.id,
116509                     margins: comp.initialConfig.margins || Ext.getClass(comp).prototype.margins,
116510                     xtype: 'header',
116511                     orientation: horiz ? 'horizontal' : 'vertical',
116512                     title: comp.title,
116513                     textCls: comp.headerTextCls,
116514                     iconCls: comp.iconCls,
116515                     baseCls: comp.baseCls + '-header',
116516                     ui: comp.ui,
116517                     indicateDrag: comp.draggable,
116518                     cls: Ext.baseCSSPrefix + 'region-collapsed-placeholder ' + Ext.baseCSSPrefix + 'region-collapsed-' + comp.collapseDirection + '-placeholder',
116519                     listeners: comp.floatable ? {
116520                         click: {
116521                             fn: function(e) {
116522                                 me.floatCollapsedPanel(e, comp);
116523                             },
116524                             element: 'el'
116525                         }
116526                     } : null
116527                 };
116528                 // Hack for IE6/7/IEQuirks's inability to display an inline-block
116529                 if ((Ext.isIE6 || Ext.isIE7 || (Ext.isIEQuirks)) && !horiz) {
116530                     placeholder.width = 25;
116531                 }
116532                 placeholder[horiz ? 'tools' : 'items'] = [{
116533                     xtype: 'tool',
116534                     client: comp,
116535                     type: 'expand-' + oppositeDirection,
116536                     handler: me.onPlaceHolderToolClick,
116537                     scope: me
116538                 }];
116539             }
116540             placeholder = me.owner.createComponent(placeholder);
116541             if (comp.isXType('panel')) {
116542                 comp.on({
116543                     titlechange: me.onRegionTitleChange,
116544                     iconchange: me.onRegionIconChange,
116545                     scope: me
116546                 });
116547             }
116548         }
116549
116550         // The collapsed Component holds a reference to its placeholder and vice versa
116551         comp.placeholder = placeholder;
116552         placeholder.comp = comp;
116553
116554         return placeholder;
116555     },
116556
116557     /**
116558      * @private
116559      * Update the placeholder title when panel title has been set or changed.
116560      */
116561     onRegionTitleChange: function(comp, newTitle) {
116562         comp.placeholder.setTitle(newTitle);
116563     },
116564
116565     /**
116566      * @private
116567      * Update the placeholder iconCls when panel iconCls has been set or changed.
116568      */
116569     onRegionIconChange: function(comp, newIconCls) {
116570         comp.placeholder.setIconCls(newIconCls);
116571     },
116572
116573     /**
116574      * @private
116575      * Calculates the size and positioning of the passed child item. Must be present because Panel's expand,
116576      * when configured with a flex, calls this method on its ownerCt's layout.
116577      * @param {Component} child The child Component to calculate the box for
116578      * @return {Object} Object containing box measurements for the child. Properties are left,top,width,height.
116579      */
116580     calculateChildBox: function(comp) {
116581         var me = this;
116582         if (me.shadowContainer.items.contains(comp)) {
116583             return me.shadowContainer.layout.calculateChildBox(comp);
116584         }
116585         else if (me.embeddedContainer && me.embeddedContainer.items.contains(comp)) {
116586             return me.embeddedContainer.layout.calculateChildBox(comp);
116587         }
116588     },
116589
116590     /**
116591      * @private
116592      * Intercepts the Panel's own collapse event and perform's substitution of the Panel
116593      * with a placeholder Header orientated in the appropriate dimension.
116594      * @param comp The Panel being collapsed.
116595      * @param direction
116596      * @param animate
116597      * @returns {Boolean} false to inhibit the Panel from performing its own collapse.
116598      */
116599     onBeforeRegionCollapse: function(comp, direction, animate) {
116600         var me = this,
116601             compEl = comp.el,
116602             miniCollapse = comp.collapseMode == 'mini',
116603             shadowContainer = comp.shadowOwnerCt,
116604             shadowLayout = shadowContainer.layout,
116605             placeholder = comp.placeholder,
116606             placeholderBox,
116607             targetSize = shadowLayout.getLayoutTargetSize(),
116608             sl = me.owner.suspendLayout,
116609             scsl = shadowContainer.suspendLayout,
116610             isNorthOrWest = (comp.region == 'north' || comp.region == 'west'); // Flag to keep the placeholder non-adjacent to any Splitter
116611
116612         // Do not trigger a layout during transition to collapsed Component
116613         me.owner.suspendLayout = true;
116614         shadowContainer.suspendLayout = true;
116615
116616         // Prevent upward notifications from downstream layouts
116617         shadowLayout.layoutBusy = true;
116618         if (shadowContainer.componentLayout) {
116619             shadowContainer.componentLayout.layoutBusy = true;
116620         }
116621         me.shadowContainer.layout.layoutBusy = true;
116622         me.layoutBusy = true;
116623         me.owner.componentLayout.layoutBusy = true;
116624
116625         // Provide a replacement Container with an expand tool
116626         if (!placeholder) {
116627             placeholder = me.getPlaceholder(comp);
116628         }
116629
116630         // placeholder already in place; show it.
116631         if (placeholder.shadowOwnerCt === shadowContainer) {
116632             placeholder.show();
116633         }
116634         // Insert the collapsed placeholder Component into the appropriate Box layout shadow Container
116635         // It must go next to its client Component, but non-adjacent to the splitter so splitter can find its collapse client.
116636         // Inject an ownerCt value pointing to the owner, border layout Container as the user will expect.
116637         else {
116638             shadowContainer.insert(shadowContainer.items.indexOf(comp) + (isNorthOrWest ? 0 : 1), placeholder);
116639             placeholder.shadowOwnerCt = shadowContainer;
116640             placeholder.ownerCt = me.owner;
116641         }
116642
116643         // Flag the collapsing Component as hidden and show the placeholder.
116644         // This causes the shadow Box layout's calculateChildBoxes to calculate the correct new arrangement.
116645         // We hide or slideOut the Component's element
116646         comp.hidden = true;
116647
116648         if (!placeholder.rendered) {
116649             shadowLayout.renderItem(placeholder, shadowLayout.innerCt);
116650         }
116651
116652         // Jobs to be done after the collapse has been done
116653         function afterCollapse() {
116654
116655             // Reinstate automatic laying out.
116656             me.owner.suspendLayout = sl;
116657             shadowContainer.suspendLayout = scsl;
116658             delete shadowLayout.layoutBusy;
116659             if (shadowContainer.componentLayout) {
116660                 delete shadowContainer.componentLayout.layoutBusy;
116661             }
116662             delete me.shadowContainer.layout.layoutBusy;
116663             delete me.layoutBusy;
116664             delete me.owner.componentLayout.layoutBusy;
116665
116666             // Fire the collapse event: The Panel has in fact been collapsed, but by substitution of an alternative Component
116667             comp.collapsed = true;
116668             comp.fireEvent('collapse', comp);
116669         }
116670
116671         /*
116672          * Set everything to the new positions. Note that we
116673          * only want to animate the collapse if it wasn't configured
116674          * initially with collapsed: true
116675          */
116676         if (comp.animCollapse && me.initialCollapsedComplete) {
116677             shadowLayout.layout();
116678             compEl.dom.style.zIndex = 100;
116679
116680             // If we're mini-collapsing, the placholder is a Splitter. We don't want it to "bounce in"
116681             if (!miniCollapse) {
116682                 placeholder.el.hide();
116683             }
116684             compEl.slideOut(me.slideDirection[comp.region], {
116685                 duration: Ext.Number.from(comp.animCollapse, Ext.fx.Anim.prototype.duration),
116686                 listeners: {
116687                     afteranimate: function() {
116688                         compEl.show().setLeftTop(-10000, -10000);
116689                         compEl.dom.style.zIndex = '';
116690
116691                         // If we're mini-collapsing, the placholder is a Splitter. We don't want it to "bounce in"
116692                        if (!miniCollapse) {
116693                             placeholder.el.slideIn(me.slideDirection[comp.region], {
116694                                 easing: 'linear',
116695                                 duration: 100
116696                             });
116697                         }
116698                         afterCollapse();
116699                     }
116700                 }
116701             });
116702         } else {
116703             compEl.setLeftTop(-10000, -10000);
116704             shadowLayout.layout();
116705             afterCollapse();
116706
116707             // Horrible workaround for https://sencha.jira.com/browse/EXTJSIV-1562
116708             if (Ext.isIE) {
116709                 placeholder.setCalculatedSize(placeholder.el.getWidth());
116710             }
116711         }
116712
116713         return false;
116714     },
116715
116716     // Hijack the expand operation to remove the placeholder and slide the region back in.
116717     onBeforeRegionExpand: function(comp, animate) {
116718         this.onPlaceHolderToolClick(null, null, null, {client: comp});
116719         return false;
116720     },
116721
116722     // Called when the collapsed placeholder is clicked to reinstate a "collapsed" (in reality hidden) Panel.
116723     onPlaceHolderToolClick: function(e, target, owner, tool) {
116724         var me = this,
116725             comp = tool.client,
116726
116727             // Hide the placeholder unless it was the Component's preexisting splitter
116728             hidePlaceholder = (comp.collapseMode != 'mini') || !comp.split,
116729             compEl = comp.el,
116730             toCompBox,
116731             placeholder = comp.placeholder,
116732             placeholderEl = placeholder.el,
116733             shadowContainer = comp.shadowOwnerCt,
116734             shadowLayout = shadowContainer.layout,
116735             curSize,
116736             sl = me.owner.suspendLayout,
116737             scsl = shadowContainer.suspendLayout,
116738             isFloating;
116739
116740         // If the slide in is still going, stop it.
116741         // This will either leave the Component in its fully floated state (which is processed below)
116742         // or in its collapsed state. Either way, we expand it..
116743         if (comp.getActiveAnimation()) {
116744             comp.stopAnimation();
116745         }
116746
116747         // If the Component is fully floated when they click the placeholder Tool,
116748         // it will be primed with a slide out animation object... so delete that
116749         // and remove the mouseout listeners
116750         if (comp.slideOutAnim) {
116751             // Remove mouse leave monitors
116752             compEl.un(comp.panelMouseMon);
116753             placeholderEl.un(comp.placeholderMouseMon);
116754
116755             delete comp.slideOutAnim;
116756             delete comp.panelMouseMon;
116757             delete comp.placeholderMouseMon;
116758
116759             // If the Panel was floated and primed with a slideOut animation, we don't want to animate its layout operation.
116760             isFloating = true;
116761         }
116762
116763         // Do not trigger a layout during transition to expanded Component
116764         me.owner.suspendLayout = true;
116765         shadowContainer.suspendLayout = true;
116766
116767         // Prevent upward notifications from downstream layouts
116768         shadowLayout.layoutBusy = true;
116769         if (shadowContainer.componentLayout) {
116770             shadowContainer.componentLayout.layoutBusy = true;
116771         }
116772         me.shadowContainer.layout.layoutBusy = true;
116773         me.layoutBusy = true;
116774         me.owner.componentLayout.layoutBusy = true;
116775
116776         // Unset the hidden and collapsed flags set in onBeforeRegionCollapse. The shadowLayout will now take it into account
116777         // Find where the shadow Box layout plans to put the expanding Component.
116778         comp.hidden = false;
116779         comp.collapsed = false;
116780         if (hidePlaceholder) {
116781             placeholder.hidden = true;
116782         }
116783         toCompBox = shadowLayout.calculateChildBox(comp);
116784
116785         // Show the collapse tool in case it was hidden by the slide-in
116786         if (comp.collapseTool) {
116787             comp.collapseTool.show();
116788         }
116789
116790         // If we're going to animate, we need to hide the component before moving it back into position
116791         if (comp.animCollapse && !isFloating) {
116792             compEl.setStyle('visibility', 'hidden');
116793         }
116794         compEl.setLeftTop(toCompBox.left, toCompBox.top);
116795
116796         // Equalize the size of the expanding Component prior to animation
116797         // in case the layout area has changed size during the time it was collapsed.
116798         curSize = comp.getSize();
116799         if (curSize.height != toCompBox.height || curSize.width != toCompBox.width) {
116800             me.setItemSize(comp, toCompBox.width, toCompBox.height);
116801         }
116802
116803         // Jobs to be done after the expand has been done
116804         function afterExpand() {
116805             // Reinstate automatic laying out.
116806             me.owner.suspendLayout = sl;
116807             shadowContainer.suspendLayout = scsl;
116808             delete shadowLayout.layoutBusy;
116809             if (shadowContainer.componentLayout) {
116810                 delete shadowContainer.componentLayout.layoutBusy;
116811             }
116812             delete me.shadowContainer.layout.layoutBusy;
116813             delete me.layoutBusy;
116814             delete me.owner.componentLayout.layoutBusy;
116815
116816             // In case it was floated out and they clicked the re-expand tool
116817             comp.removeCls(Ext.baseCSSPrefix + 'border-region-slide-in');
116818
116819             // Fire the expand event: The Panel has in fact been expanded, but by removal of an alternative Component
116820             comp.fireEvent('expand', comp);
116821         }
116822
116823         // Hide the placeholder
116824         if (hidePlaceholder) {
116825             placeholder.el.hide();
116826         }
116827
116828         // Slide the expanding Component to its new position.
116829         // When that is done, layout the layout.
116830         if (comp.animCollapse && !isFloating) {
116831             compEl.dom.style.zIndex = 100;
116832             compEl.slideIn(me.slideDirection[comp.region], {
116833                 duration: Ext.Number.from(comp.animCollapse, Ext.fx.Anim.prototype.duration),
116834                 listeners: {
116835                     afteranimate: function() {
116836                         compEl.dom.style.zIndex = '';
116837                         comp.hidden = false;
116838                         shadowLayout.onLayout();
116839                         afterExpand();
116840                     }
116841                 }
116842             });
116843         } else {
116844             shadowLayout.onLayout();
116845             afterExpand();
116846         }
116847     },
116848
116849     floatCollapsedPanel: function(e, comp) {
116850
116851         if (comp.floatable === false) {
116852             return;
116853         }
116854
116855         var me = this,
116856             compEl = comp.el,
116857             placeholder = comp.placeholder,
116858             placeholderEl = placeholder.el,
116859             shadowContainer = comp.shadowOwnerCt,
116860             shadowLayout = shadowContainer.layout,
116861             placeholderBox = shadowLayout.getChildBox(placeholder),
116862             scsl = shadowContainer.suspendLayout,
116863             curSize, toCompBox, compAnim;
116864
116865         // Ignore clicks on tools.
116866         if (e.getTarget('.' + Ext.baseCSSPrefix + 'tool')) {
116867             return;
116868         }
116869
116870         // It's *being* animated, ignore the click.
116871         // Possible future enhancement: Stop and *reverse* the current active Fx.
116872         if (compEl.getActiveAnimation()) {
116873             return;
116874         }
116875
116876         // If the Component is already fully floated when they click the placeholder,
116877         // it will be primed with a slide out animation object... so slide it out.
116878         if (comp.slideOutAnim) {
116879             me.slideOutFloatedComponent(comp);
116880             return;
116881         }
116882
116883         // Function to be called when the mouse leaves the floated Panel
116884         // Slide out when the mouse leaves the region bounded by the slid Component and its placeholder.
116885         function onMouseLeaveFloated(e) {
116886             var slideRegion = compEl.getRegion().union(placeholderEl.getRegion()).adjust(1, -1, -1, 1);
116887
116888             // If mouse is not within slide Region, slide it out
116889             if (!slideRegion.contains(e.getPoint())) {
116890                 me.slideOutFloatedComponent(comp);
116891             }
116892         }
116893
116894         // Monitor for mouseouting of the placeholder. Hide it if they exit for half a second or more
116895         comp.placeholderMouseMon = placeholderEl.monitorMouseLeave(500, onMouseLeaveFloated);
116896
116897         // Do not trigger a layout during slide out of the Component
116898         shadowContainer.suspendLayout = true;
116899
116900         // Prevent upward notifications from downstream layouts
116901         me.layoutBusy = true;
116902         me.owner.componentLayout.layoutBusy = true;
116903
116904         // The collapse tool is hidden while slid.
116905         // It is re-shown on expand.
116906         if (comp.collapseTool) {
116907             comp.collapseTool.hide();
116908         }
116909
116910         // Set flags so that the layout will calculate the boxes for what we want
116911         comp.hidden = false;
116912         comp.collapsed = false;
116913         placeholder.hidden = true;
116914
116915         // Recalculate new arrangement of the Component being floated.
116916         toCompBox = shadowLayout.calculateChildBox(comp);
116917         placeholder.hidden = false;
116918
116919         // Component to appear just after the placeholder, whatever "after" means in the context of the shadow Box layout.
116920         if (comp.region == 'north' || comp.region == 'west') {
116921             toCompBox[shadowLayout.parallelBefore] += placeholderBox[shadowLayout.parallelPrefix] - 1;
116922         } else {
116923             toCompBox[shadowLayout.parallelBefore] -= (placeholderBox[shadowLayout.parallelPrefix] - 1);
116924         }
116925         compEl.setStyle('visibility', 'hidden');
116926         compEl.setLeftTop(toCompBox.left, toCompBox.top);
116927
116928         // Equalize the size of the expanding Component prior to animation
116929         // in case the layout area has changed size during the time it was collapsed.
116930         curSize = comp.getSize();
116931         if (curSize.height != toCompBox.height || curSize.width != toCompBox.width) {
116932             me.setItemSize(comp, toCompBox.width, toCompBox.height);
116933         }
116934
116935         // This animation slides the collapsed Component's el out to just beyond its placeholder
116936         compAnim = {
116937             listeners: {
116938                 afteranimate: function() {
116939                     shadowContainer.suspendLayout = scsl;
116940                     delete me.layoutBusy;
116941                     delete me.owner.componentLayout.layoutBusy;
116942
116943                     // Prime the Component with an Anim config object to slide it back out
116944                     compAnim.listeners = {
116945                         afterAnimate: function() {
116946                             compEl.show().removeCls(Ext.baseCSSPrefix + 'border-region-slide-in').setLeftTop(-10000, -10000);
116947
116948                             // Reinstate the correct, current state after slide out animation finishes
116949                             comp.hidden = true;
116950                             comp.collapsed = true;
116951                             delete comp.slideOutAnim;
116952                             delete comp.panelMouseMon;
116953                             delete comp.placeholderMouseMon;
116954                         }
116955                     };
116956                     comp.slideOutAnim = compAnim;
116957                 }
116958             },
116959             duration: 500
116960         };
116961
116962         // Give the element the correct class which places it at a high z-index
116963         compEl.addCls(Ext.baseCSSPrefix + 'border-region-slide-in');
116964
116965         // Begin the slide in
116966         compEl.slideIn(me.slideDirection[comp.region], compAnim);
116967
116968         // Monitor for mouseouting of the slid area. Hide it if they exit for half a second or more
116969         comp.panelMouseMon = compEl.monitorMouseLeave(500, onMouseLeaveFloated);
116970
116971     },
116972
116973     slideOutFloatedComponent: function(comp) {
116974         var compEl = comp.el,
116975             slideOutAnim;
116976
116977         // Remove mouse leave monitors
116978         compEl.un(comp.panelMouseMon);
116979         comp.placeholder.el.un(comp.placeholderMouseMon);
116980
116981         // Slide the Component out
116982         compEl.slideOut(this.slideDirection[comp.region], comp.slideOutAnim);
116983
116984         delete comp.slideOutAnim;
116985         delete comp.panelMouseMon;
116986         delete comp.placeholderMouseMon;
116987     },
116988
116989     /*
116990      * @private
116991      * Ensure any collapsed placeholder Component is destroyed along with its region.
116992      * Can't do this in onDestroy because they may remove a Component and use it elsewhere.
116993      */
116994     onRegionDestroy: function(comp) {
116995         var placeholder = comp.placeholder;
116996         if (placeholder) {
116997             delete placeholder.ownerCt;
116998             placeholder.destroy();
116999         }
117000     },
117001
117002     /*
117003      * @private
117004      * Ensure any shadow Containers are destroyed.
117005      * Ensure we don't keep references to Components.
117006      */
117007     onDestroy: function() {
117008         var me = this,
117009             shadowContainer = me.shadowContainer,
117010             embeddedContainer = me.embeddedContainer;
117011
117012         if (shadowContainer) {
117013             delete shadowContainer.ownerCt;
117014             Ext.destroy(shadowContainer);
117015         }
117016
117017         if (embeddedContainer) {
117018             delete embeddedContainer.ownerCt;
117019             Ext.destroy(embeddedContainer);
117020         }
117021         delete me.regions;
117022         delete me.splitters;
117023         delete me.shadowContainer;
117024         delete me.embeddedContainer;
117025         me.callParent(arguments);
117026     }
117027 });
117028
117029 /**
117030  * @class Ext.layout.container.Card
117031  * @extends Ext.layout.container.AbstractCard
117032   * <p>This layout manages multiple child Components, each fitted to the Container, where only a single child Component can be
117033   * visible at any given time.  This layout style is most commonly used for wizards, tab implementations, etc.
117034   * This class is intended to be extended or created via the layout:'card' {@link Ext.container.Container#layout} config,
117035   * and should generally not need to be created directly via the new keyword.</p>
117036   * <p>The CardLayout's focal method is {@link #setActiveItem}.  Since only one panel is displayed at a time,
117037   * the only way to move from one Component to the next is by calling setActiveItem, passing the id or index of
117038   * the next panel to display.  The layout itself does not provide a user interface for handling this navigation,
117039   * so that functionality must be provided by the developer.</p>
117040   * <p>In the following example, a simplistic wizard setup is demonstrated.  A button bar is added
117041   * to the footer of the containing panel to provide navigation buttons.  The buttons will be handled by a
117042   * common navigation routine -- for this example, the implementation of that routine has been ommitted since
117043   * it can be any type of custom logic.  Note that other uses of a CardLayout (like a tab control) would require a
117044   * completely different implementation.  For serious implementations, a better approach would be to extend
117045   * CardLayout to provide the custom functionality needed.  
117046   * {@img Ext.layout.container.Card/Ext.layout.container.Card.png Ext.layout.container.Card container layout}
117047   * Example usage:</p>
117048   * <pre><code>
117049     var navHandler = function(direction){
117050          // This routine could contain business logic required to manage the navigation steps.
117051          // It would call setActiveItem as needed, manage navigation button state, handle any
117052          // branching logic that might be required, handle alternate actions like cancellation
117053          // or finalization, etc.  A complete wizard implementation could get pretty
117054          // sophisticated depending on the complexity required, and should probably be
117055          // done as a subclass of CardLayout in a real-world implementation.
117056      };
117057
117058     Ext.create('Ext.panel.Panel', {
117059         title: 'Example Wizard',
117060         width: 300,
117061         height: 200,
117062         layout: 'card',
117063         activeItem: 0, // make sure the active item is set on the container config!
117064         bodyStyle: 'padding:15px',
117065         defaults: {
117066             // applied to each contained panel
117067             border:false
117068         },
117069         // just an example of one possible navigation scheme, using buttons
117070         bbar: [
117071         {
117072             id: 'move-prev',
117073             text: 'Back',
117074             handler: navHandler(this, [-1]),
117075             disabled: true
117076         },
117077         '->', // greedy spacer so that the buttons are aligned to each side
117078         {
117079             id: 'move-next',
117080             text: 'Next',
117081             handler: navHandler(this, [1])
117082         }],
117083         // the panels (or "cards") within the layout
117084         items: [{
117085             id: 'card-0',
117086             html: '<h1>Welcome to the Wizard!</h1><p>Step 1 of 3</p>'
117087         },{
117088             id: 'card-1',
117089             html: '<p>Step 2 of 3</p>'
117090         },{
117091             id: 'card-2',
117092             html: '<h1>Congratulations!</h1><p>Step 3 of 3 - Complete</p>'
117093         }],
117094         renderTo: Ext.getBody()
117095     });  
117096  </code></pre>
117097   */
117098 Ext.define('Ext.layout.container.Card', {
117099
117100     /* Begin Definitions */
117101
117102     alias: ['layout.card'],
117103     alternateClassName: 'Ext.layout.CardLayout',
117104
117105     extend: 'Ext.layout.container.AbstractCard',
117106
117107     /* End Definitions */
117108
117109     setActiveItem: function(newCard) {
117110         var me = this,
117111             owner = me.owner,
117112             oldCard = me.activeItem,
117113             newIndex;
117114
117115         // Block upward layouts until we are done.
117116         me.layoutBusy = true;
117117
117118         newCard = me.parseActiveItem(newCard);
117119         newIndex = owner.items.indexOf(newCard);
117120
117121         // If the card is not a child of the owner, then add it
117122         if (newIndex == -1) {
117123             newIndex = owner.items.items.length;
117124             owner.add(newCard);
117125         }
117126
117127         // Is this a valid, different card?
117128         if (newCard && oldCard != newCard) {
117129             // If the card has not been rendered yet, now is the time to do so.
117130             if (!newCard.rendered) {
117131                 me.renderItem(newCard, me.getRenderTarget(), owner.items.length);
117132                 me.configureItem(newCard, 0);
117133             }
117134
117135             me.activeItem = newCard;
117136
117137             // Fire the beforeactivate and beforedeactivate events on the cards
117138             if (newCard.fireEvent('beforeactivate', newCard, oldCard) === false) {
117139                 me.layoutBusy = false;
117140                 return false;
117141             }
117142             if (oldCard && oldCard.fireEvent('beforedeactivate', oldCard, newCard) === false) {
117143                 me.layoutBusy = false;
117144                 return false;
117145             }
117146
117147             // If the card hasnt been sized yet, do it now
117148             if (!me.sizeAllCards) {
117149                 me.setItemBox(newCard, me.getTargetBox());
117150             }
117151             else {
117152                 // onLayout calls setItemBox
117153                 me.onLayout();
117154             }
117155
117156             if (oldCard) {
117157                 if (me.hideInactive) {
117158                     oldCard.hide();
117159                 }
117160                 oldCard.fireEvent('deactivate', oldCard, newCard);
117161             }
117162
117163             // Make sure the new card is shown
117164             if (newCard.hidden) {
117165                 newCard.show();
117166             }
117167
117168             newCard.fireEvent('activate', newCard, oldCard);
117169
117170             me.layoutBusy = false;
117171
117172             if (!me.sizeAllCards) {
117173                 if (!owner.componentLayout.layoutBusy) {
117174                     me.onLayout();
117175                 }
117176             }
117177             return newCard;
117178         }
117179
117180         me.layoutBusy = false;
117181         return false;
117182     }
117183 });
117184 /**
117185  * @class Ext.layout.container.Column
117186  * @extends Ext.layout.container.Auto
117187  * <p>This is the layout style of choice for creating structural layouts in a multi-column format where the width of
117188  * each column can be specified as a percentage or fixed width, but the height is allowed to vary based on the content.
117189  * This class is intended to be extended or created via the layout:'column' {@link Ext.container.Container#layout} config,
117190  * and should generally not need to be created directly via the new keyword.</p>
117191  * <p>ColumnLayout does not have any direct config options (other than inherited ones), but it does support a
117192  * specific config property of <b><tt>columnWidth</tt></b> that can be included in the config of any panel added to it.  The
117193  * layout will use the columnWidth (if present) or width of each panel during layout to determine how to size each panel.
117194  * If width or columnWidth is not specified for a given panel, its width will default to the panel's width (or auto).</p>
117195  * <p>The width property is always evaluated as pixels, and must be a number greater than or equal to 1.
117196  * The columnWidth property is always evaluated as a percentage, and must be a decimal value greater than 0 and
117197  * less than 1 (e.g., .25).</p>
117198  * <p>The basic rules for specifying column widths are pretty simple.  The logic makes two passes through the
117199  * set of contained panels.  During the first layout pass, all panels that either have a fixed width or none
117200  * specified (auto) are skipped, but their widths are subtracted from the overall container width.  During the second
117201  * pass, all panels with columnWidths are assigned pixel widths in proportion to their percentages based on
117202  * the total <b>remaining</b> container width.  In other words, percentage width panels are designed to fill the space
117203  * left over by all the fixed-width and/or auto-width panels.  Because of this, while you can specify any number of columns
117204  * with different percentages, the columnWidths must always add up to 1 (or 100%) when added together, otherwise your
117205  * layout may not render as expected.  
117206  * {@img Ext.layout.container.Column/Ext.layout.container.Column1.png Ext.layout.container.Column container layout}
117207  * Example usage:</p>
117208  * <pre><code>
117209     // All columns are percentages -- they must add up to 1
117210     Ext.create('Ext.panel.Panel', {
117211         title: 'Column Layout - Percentage Only',
117212         width: 350,
117213         height: 250,
117214         layout:'column',
117215         items: [{
117216             title: 'Column 1',
117217             columnWidth: .25
117218         },{
117219             title: 'Column 2',
117220             columnWidth: .55
117221         },{
117222             title: 'Column 3',
117223             columnWidth: .20
117224         }],
117225         renderTo: Ext.getBody()
117226     }); 
117227
117228 // {@img Ext.layout.container.Column/Ext.layout.container.Column2.png Ext.layout.container.Column container layout}
117229 // Mix of width and columnWidth -- all columnWidth values must add up
117230 // to 1. The first column will take up exactly 120px, and the last two
117231 // columns will fill the remaining container width.
117232
117233     Ext.create('Ext.Panel', {
117234         title: 'Column Layout - Mixed',
117235         width: 350,
117236         height: 250,
117237         layout:'column',
117238         items: [{
117239             title: 'Column 1',
117240             width: 120
117241         },{
117242             title: 'Column 2',
117243             columnWidth: .7
117244         },{
117245             title: 'Column 3',
117246             columnWidth: .3
117247         }],
117248         renderTo: Ext.getBody()
117249     }); 
117250 </code></pre>
117251  */
117252 Ext.define('Ext.layout.container.Column', {
117253
117254     extend: 'Ext.layout.container.Auto',
117255     alias: ['layout.column'],
117256     alternateClassName: 'Ext.layout.ColumnLayout',
117257
117258     type: 'column',
117259
117260     itemCls: Ext.baseCSSPrefix + 'column',
117261
117262     targetCls: Ext.baseCSSPrefix + 'column-layout-ct',
117263
117264     scrollOffset: 0,
117265
117266     bindToOwnerCtComponent: false,
117267
117268     getRenderTarget : function() {
117269         if (!this.innerCt) {
117270
117271             // the innerCt prevents wrapping and shuffling while
117272             // the container is resizing
117273             this.innerCt = this.getTarget().createChild({
117274                 cls: Ext.baseCSSPrefix + 'column-inner'
117275             });
117276
117277             // Column layout uses natural HTML flow to arrange the child items.
117278             // To ensure that all browsers (I'm looking at you IE!) add the bottom margin of the last child to the
117279             // containing element height, we create a zero-sized element with style clear:both to force a "new line"
117280             this.clearEl = this.innerCt.createChild({
117281                 cls: Ext.baseCSSPrefix + 'clear',
117282                 role: 'presentation'
117283             });
117284         }
117285         return this.innerCt;
117286     },
117287
117288     // private
117289     onLayout : function() {
117290         var me = this,
117291             target = me.getTarget(),
117292             items = me.getLayoutItems(),
117293             len = items.length,
117294             item,
117295             i,
117296             parallelMargins = [],
117297             itemParallelMargins,
117298             size,
117299             availableWidth,
117300             columnWidth;
117301
117302         size = me.getLayoutTargetSize();
117303         if (size.width < len * 10) { // Don't lay out in impossibly small target (probably display:none, or initial, unsized Container)
117304             return;
117305         }
117306
117307         // On the first pass, for all except IE6-7, we lay out the items with no scrollbars visible using style overflow: hidden.
117308         // If, after the layout, it is detected that there is vertical overflow,
117309         // we will recurse back through here. Do not adjust overflow style at that time.
117310         if (me.adjustmentPass) {
117311             if (Ext.isIE6 || Ext.isIE7 || Ext.isIEQuirks) {
117312                 size.width = me.adjustedWidth;
117313             }
117314         } else {
117315             i = target.getStyle('overflow');
117316             if (i && i != 'hidden') {
117317                 me.autoScroll = true;
117318                 if (!(Ext.isIE6 || Ext.isIE7 || Ext.isIEQuirks)) {
117319                     target.setStyle('overflow', 'hidden');
117320                     size = me.getLayoutTargetSize();
117321                 }
117322             }
117323         }
117324
117325         availableWidth = size.width - me.scrollOffset;
117326         me.innerCt.setWidth(availableWidth);
117327
117328         // some columns can be percentages while others are fixed
117329         // so we need to make 2 passes
117330         for (i = 0; i < len; i++) {
117331             item = items[i];
117332             itemParallelMargins = parallelMargins[i] = item.getEl().getMargin('lr');
117333             if (!item.columnWidth) {
117334                 availableWidth -= (item.getWidth() + itemParallelMargins);
117335             }
117336         }
117337
117338         availableWidth = availableWidth < 0 ? 0 : availableWidth;
117339         for (i = 0; i < len; i++) {
117340             item = items[i];
117341             if (item.columnWidth) {
117342                 columnWidth = Math.floor(item.columnWidth * availableWidth) - parallelMargins[i];
117343                 if (item.getWidth() != columnWidth) {
117344                     me.setItemSize(item, columnWidth, item.height);
117345                 }
117346             }
117347         }
117348
117349         // After the first pass on an autoScroll layout, restore the overflow settings if it had been changed (only changed for non-IE6)
117350         if (!me.adjustmentPass && me.autoScroll) {
117351
117352             // If there's a vertical overflow, relay with scrollbars
117353             target.setStyle('overflow', 'auto');
117354             me.adjustmentPass = (target.dom.scrollHeight > size.height);
117355             if (Ext.isIE6 || Ext.isIE7 || Ext.isIEQuirks) {
117356                 me.adjustedWidth = size.width - Ext.getScrollBarWidth();
117357             } else {
117358                 target.setStyle('overflow', 'auto');
117359             }
117360
117361             // If the layout caused height overflow, recurse back and recalculate (with overflow setting restored on non-IE6)
117362             if (me.adjustmentPass) {
117363                 me.onLayout();
117364             }
117365         }
117366         delete me.adjustmentPass;
117367     }
117368 });
117369 /**
117370  * @class Ext.layout.container.Table
117371  * @extends Ext.layout.container.Auto
117372  * <p>This layout allows you to easily render content into an HTML table.  The total number of columns can be
117373  * specified, and rowspan and colspan can be used to create complex layouts within the table.
117374  * This class is intended to be extended or created via the <code>layout: {type: 'table'}</code>
117375  * {@link Ext.container.Container#layout} config, and should generally not need to be created directly via the new keyword.</p>
117376  * <p>Note that when creating a layout via config, the layout-specific config properties must be passed in via
117377  * the {@link Ext.container.Container#layout} object which will then be applied internally to the layout.  In the
117378  * case of TableLayout, the only valid layout config properties are {@link #columns} and {@link #tableAttrs}.
117379  * However, the items added to a TableLayout can supply the following table-specific config properties:</p>
117380  * <ul>
117381  * <li><b>rowspan</b> Applied to the table cell containing the item.</li>
117382  * <li><b>colspan</b> Applied to the table cell containing the item.</li>
117383  * <li><b>cellId</b> An id applied to the table cell containing the item.</li>
117384  * <li><b>cellCls</b> A CSS class name added to the table cell containing the item.</li>
117385  * </ul>
117386  * <p>The basic concept of building up a TableLayout is conceptually very similar to building up a standard
117387  * HTML table.  You simply add each panel (or "cell") that you want to include along with any span attributes
117388  * specified as the special config properties of rowspan and colspan which work exactly like their HTML counterparts.
117389  * Rather than explicitly creating and nesting rows and columns as you would in HTML, you simply specify the
117390  * total column count in the layoutConfig and start adding panels in their natural order from left to right,
117391  * top to bottom.  The layout will automatically figure out, based on the column count, rowspans and colspans,
117392  * how to position each panel within the table.  Just like with HTML tables, your rowspans and colspans must add
117393  * up correctly in your overall layout or you'll end up with missing and/or extra cells!  Example usage:</p>
117394  * {@img Ext.layout.container.Table/Ext.layout.container.Table.png Ext.layout.container.Table container layout}
117395  * <pre><code>
117396 // This code will generate a layout table that is 3 columns by 2 rows
117397 // with some spanning included.  The basic layout will be:
117398 // +--------+-----------------+
117399 // |   A    |   B             |
117400 // |        |--------+--------|
117401 // |        |   C    |   D    |
117402 // +--------+--------+--------+
117403     Ext.create('Ext.panel.Panel', {
117404         title: 'Table Layout',
117405         width: 300,
117406         height: 150,
117407         layout: {
117408             type: 'table',
117409             // The total column count must be specified here
117410             columns: 3
117411         },
117412         defaults: {
117413             // applied to each contained panel
117414             bodyStyle:'padding:20px'
117415         },
117416         items: [{
117417             html: '<p>Cell A content</p>',
117418             rowspan: 2
117419         },{
117420             html: '<p>Cell B content</p>',
117421             colspan: 2
117422         },{
117423             html: '<p>Cell C content</p>',
117424             cellCls: 'highlight'
117425         },{
117426             html: '<p>Cell D content</p>'
117427         }],
117428         renderTo: Ext.getBody()
117429     });
117430 </code></pre>
117431  */
117432
117433 Ext.define('Ext.layout.container.Table', {
117434
117435     /* Begin Definitions */
117436
117437     alias: ['layout.table'],
117438     extend: 'Ext.layout.container.Auto',
117439     alternateClassName: 'Ext.layout.TableLayout',
117440
117441     /* End Definitions */
117442
117443     /**
117444      * @cfg {Number} columns
117445      * The total number of columns to create in the table for this layout.  If not specified, all Components added to
117446      * this layout will be rendered into a single row using one column per Component.
117447      */
117448
117449     // private
117450     monitorResize:false,
117451
117452     type: 'table',
117453
117454     // Table layout is a self-sizing layout. When an item of for example, a dock layout, the Panel must expand to accommodate
117455     // a table layout. See in particular AbstractDock::onLayout for use of this flag.
117456     autoSize: true,
117457
117458     clearEl: true, // Base class will not create it if already truthy. Not needed in tables.
117459
117460     targetCls: Ext.baseCSSPrefix + 'table-layout-ct',
117461     tableCls: Ext.baseCSSPrefix + 'table-layout',
117462     cellCls: Ext.baseCSSPrefix + 'table-layout-cell',
117463
117464     /**
117465      * @cfg {Object} tableAttrs
117466      * <p>An object containing properties which are added to the {@link Ext.core.DomHelper DomHelper} specification
117467      * used to create the layout's <tt>&lt;table&gt;</tt> element. Example:</p><pre><code>
117468 {
117469     xtype: 'panel',
117470     layout: {
117471         type: 'table',
117472         columns: 3,
117473         tableAttrs: {
117474             style: {
117475                 width: '100%'
117476             }
117477         }
117478     }
117479 }</code></pre>
117480      */
117481     tableAttrs:null,
117482
117483     /**
117484      * @private
117485      * Iterates over all passed items, ensuring they are rendered in a cell in the proper
117486      * location in the table structure.
117487      */
117488     renderItems: function(items) {
117489         var tbody = this.getTable().tBodies[0],
117490             rows = tbody.rows,
117491             i = 0,
117492             len = items.length,
117493             cells, curCell, rowIdx, cellIdx, item, trEl, tdEl, itemCt;
117494
117495         // Calculate the correct cell structure for the current items
117496         cells = this.calculateCells(items);
117497
117498         // Loop over each cell and compare to the current cells in the table, inserting/
117499         // removing/moving cells as needed, and making sure each item is rendered into
117500         // the correct cell.
117501         for (; i < len; i++) {
117502             curCell = cells[i];
117503             rowIdx = curCell.rowIdx;
117504             cellIdx = curCell.cellIdx;
117505             item = items[i];
117506
117507             // If no row present, create and insert one
117508             trEl = rows[rowIdx];
117509             if (!trEl) {
117510                 trEl = tbody.insertRow(rowIdx);
117511             }
117512
117513             // If no cell present, create and insert one
117514             itemCt = tdEl = Ext.get(trEl.cells[cellIdx] || trEl.insertCell(cellIdx));
117515             if (this.needsDivWrap()) { //create wrapper div if needed - see docs below
117516                 itemCt = tdEl.first() || tdEl.createChild({tag: 'div'});
117517                 itemCt.setWidth(null);
117518             }
117519
117520             // Render or move the component into the cell
117521             if (!item.rendered) {
117522                 this.renderItem(item, itemCt, 0);
117523             }
117524             else if (!this.isValidParent(item, itemCt, 0)) {
117525                 this.moveItem(item, itemCt, 0);
117526             }
117527
117528             // Set the cell properties
117529             tdEl.set({
117530                 colSpan: item.colspan || 1,
117531                 rowSpan: item.rowspan || 1,
117532                 id: item.cellId || '',
117533                 cls: this.cellCls + ' ' + (item.cellCls || '')
117534             });
117535
117536             // If at the end of a row, remove any extra cells
117537             if (!cells[i + 1] || cells[i + 1].rowIdx !== rowIdx) {
117538                 cellIdx++;
117539                 while (trEl.cells[cellIdx]) {
117540                     trEl.deleteCell(cellIdx);
117541                 }
117542             }
117543         }
117544
117545         // Delete any extra rows
117546         rowIdx++;
117547         while (tbody.rows[rowIdx]) {
117548             tbody.deleteRow(rowIdx);
117549         }
117550     },
117551
117552     afterLayout: function() {
117553         this.callParent();
117554
117555         if (this.needsDivWrap()) {
117556             // set wrapper div width to match layed out item - see docs below
117557             Ext.Array.forEach(this.getLayoutItems(), function(item) {
117558                 Ext.fly(item.el.dom.parentNode).setWidth(item.getWidth());
117559             });
117560         }
117561     },
117562
117563     /**
117564      * @private
117565      * Determine the row and cell indexes for each component, taking into consideration
117566      * the number of columns and each item's configured colspan/rowspan values.
117567      * @param {Array} items The layout components
117568      * @return {Array} List of row and cell indexes for each of the components
117569      */
117570     calculateCells: function(items) {
117571         var cells = [],
117572             rowIdx = 0,
117573             colIdx = 0,
117574             cellIdx = 0,
117575             totalCols = this.columns || Infinity,
117576             rowspans = [], //rolling list of active rowspans for each column
117577             i = 0, j,
117578             len = items.length,
117579             item;
117580
117581         for (; i < len; i++) {
117582             item = items[i];
117583
117584             // Find the first available row/col slot not taken up by a spanning cell
117585             while (colIdx >= totalCols || rowspans[colIdx] > 0) {
117586                 if (colIdx >= totalCols) {
117587                     // move down to next row
117588                     colIdx = 0;
117589                     cellIdx = 0;
117590                     rowIdx++;
117591
117592                     // decrement all rowspans
117593                     for (j = 0; j < totalCols; j++) {
117594                         if (rowspans[j] > 0) {
117595                             rowspans[j]--;
117596                         }
117597                     }
117598                 } else {
117599                     colIdx++;
117600                 }
117601             }
117602
117603             // Add the cell info to the list
117604             cells.push({
117605                 rowIdx: rowIdx,
117606                 cellIdx: cellIdx
117607             });
117608
117609             // Increment
117610             rowspans[colIdx] = item.rowspan || 1;
117611             colIdx += item.colspan || 1;
117612             cellIdx++;
117613         }
117614
117615         return cells;
117616     },
117617
117618     /**
117619      * @private
117620      * Return the layout's table element, creating it if necessary.
117621      */
117622     getTable: function() {
117623         var table = this.table;
117624         if (!table) {
117625             table = this.table = this.getTarget().createChild(
117626                 Ext.apply({
117627                     tag: 'table',
117628                     role: 'presentation',
117629                     cls: this.tableCls,
117630                     cellspacing: 0, //TODO should this be specified or should CSS handle it?
117631                     cn: {tag: 'tbody'}
117632                 }, this.tableAttrs),
117633                 null, true
117634             );
117635         }
117636         return table;
117637     },
117638
117639     /**
117640      * @private
117641      * Opera 10.5 has a bug where if a table cell's child has box-sizing:border-box and padding, it
117642      * will include that padding in the size of the cell, making it always larger than the
117643      * shrink-wrapped size of its contents. To get around this we have to wrap the contents in a div
117644      * and then set that div's width to match the item rendered within it afterLayout. This method
117645      * determines whether we need the wrapper div; it currently does a straight UA sniff as this bug
117646      * seems isolated to just Opera 10.5, but feature detection could be added here if needed.
117647      */
117648     needsDivWrap: function() {
117649         return Ext.isOpera10_5;
117650     }
117651 });
117652 /**
117653  * @class Ext.menu.Item
117654  * @extends Ext.Component
117655
117656  * A base class for all menu items that require menu-related functionality such as click handling,
117657  * sub-menus, icons, etc.
117658  * {@img Ext.menu.Menu/Ext.menu.Menu.png Ext.menu.Menu component}
117659 __Example Usage:__
117660     Ext.create('Ext.menu.Menu', {
117661                 width: 100,
117662                 height: 100,
117663                 floating: false,  // usually you want this set to True (default)
117664                 renderTo: Ext.getBody(),  // usually rendered by it's containing component
117665                 items: [{
117666                     text: 'icon item',
117667                     iconCls: 'add16'
117668                 },{
117669                         text: 'text item',
117670                 },{                        
117671                         text: 'plain item',
117672                         plain: true        
117673                 }]
117674         }); 
117675
117676  * @xtype menuitem
117677  * @markdown
117678  * @constructor
117679  * @param {Object} config The config object
117680  */
117681 Ext.define('Ext.menu.Item', {
117682     extend: 'Ext.Component',
117683     alias: 'widget.menuitem',
117684     alternateClassName: 'Ext.menu.TextItem',
117685     
117686     /**
117687      * @property {Boolean} activated
117688      * Whether or not this item is currently activated
117689      */
117690
117691     /**
117692      * @cfg {String} activeCls
117693      * The CSS class added to the menu item when the item is activated (focused/mouseover).
117694      * Defaults to `Ext.baseCSSPrefix + 'menu-item-active'`.
117695      * @markdown
117696      */
117697     activeCls: Ext.baseCSSPrefix + 'menu-item-active',
117698     
117699     /**
117700      * @cfg {String} ariaRole @hide
117701      */
117702     ariaRole: 'menuitem',
117703     
117704     /**
117705      * @cfg {Boolean} canActivate
117706      * Whether or not this menu item can be activated when focused/mouseovered. Defaults to `true`.
117707      * @markdown
117708      */
117709     canActivate: true,
117710     
117711     /**
117712      * @cfg {Number} clickHideDelay
117713      * The delay in milliseconds to wait before hiding the menu after clicking the menu item.
117714      * This only has an effect when `hideOnClick: true`. Defaults to `1`.
117715      * @markdown
117716      */
117717     clickHideDelay: 1,
117718     
117719     /**
117720      * @cfg {Boolean} destroyMenu
117721      * Whether or not to destroy any associated sub-menu when this item is destroyed. Defaults to `true`.
117722      */
117723     destroyMenu: true,
117724     
117725     /**
117726      * @cfg {String} disabledCls
117727      * The CSS class added to the menu item when the item is disabled.
117728      * Defaults to `Ext.baseCSSPrefix + 'menu-item-disabled'`.
117729      * @markdown
117730      */
117731     disabledCls: Ext.baseCSSPrefix + 'menu-item-disabled',
117732     
117733     /**
117734      * @cfg {String} href
117735      * The href attribute to use for the underlying anchor link. Defaults to `#`.
117736      * @markdown
117737      */
117738      
117739      /**
117740       * @cfg {String} hrefTarget
117741       * The target attribute to use for the underlying anchor link. Defaults to `undefined`.
117742       * @markdown
117743       */
117744     
117745     /**
117746      * @cfg {Boolean} hideOnClick
117747      * Whether to not to hide the owning menu when this item is clicked. Defaults to `true`.
117748      * @markdown
117749      */
117750     hideOnClick: true,
117751     
117752     /**
117753      * @cfg {String} icon
117754      * The path to an icon to display in this item. Defaults to `Ext.BLANK_IMAGE_URL`.
117755      * @markdown
117756      */
117757      
117758     /**
117759      * @cfg {String} iconCls
117760      * A CSS class that specifies a `background-image` to use as the icon for this item. Defaults to `undefined`.
117761      * @markdown
117762      */
117763     
117764     isMenuItem: true,
117765     
117766     /**
117767      * @cfg {Mixed} menu
117768      * Either an instance of {@link Ext.menu.Menu} or a config object for an {@link Ext.menu.Menu}
117769      * which will act as a sub-menu to this item.
117770      * @markdown
117771      * @property {Ext.menu.Menu} menu The sub-menu associated with this item, if one was configured.
117772      */
117773     
117774     /**
117775      * @cfg {String} menuAlign
117776      * The default {@link Ext.core.Element#getAlignToXY Ext.Element.getAlignToXY} anchor position value for this
117777      * item's sub-menu relative to this item's position. Defaults to `'tl-tr?'`.
117778      * @markdown
117779      */
117780     menuAlign: 'tl-tr?',
117781     
117782     /**
117783      * @cfg {Number} menuExpandDelay
117784      * The delay in milliseconds before this item's sub-menu expands after this item is moused over. Defaults to `200`.
117785      * @markdown
117786      */
117787     menuExpandDelay: 200,
117788     
117789     /**
117790      * @cfg {Number} menuHideDelay
117791      * The delay in milliseconds before this item's sub-menu hides after this item is moused out. Defaults to `200`.
117792      * @markdown
117793      */
117794     menuHideDelay: 200,
117795     
117796     /**
117797      * @cfg {Boolean} plain
117798      * Whether or not this item is plain text/html with no icon or visual activation. Defaults to `false`.
117799      * @markdown
117800      */
117801     
117802     renderTpl: [
117803         '<tpl if="plain">',
117804             '{text}',
117805         '</tpl>',
117806         '<tpl if="!plain">',
117807             '<a class="' + Ext.baseCSSPrefix + 'menu-item-link" href="{href}" <tpl if="hrefTarget">target="{hrefTarget}"</tpl> hidefocus="true" unselectable="on">',
117808                 '<img src="{icon}" class="' + Ext.baseCSSPrefix + 'menu-item-icon {iconCls}" />',
117809                 '<span class="' + Ext.baseCSSPrefix + 'menu-item-text" <tpl if="menu">style="margin-right: 17px;"</tpl> >{text}</span>',
117810                 '<tpl if="menu">',
117811                     '<img src="' + Ext.BLANK_IMAGE_URL + '" class="' + Ext.baseCSSPrefix + 'menu-item-arrow" />',
117812                 '</tpl>',
117813             '</a>',
117814         '</tpl>'
117815     ],
117816     
117817     maskOnDisable: false,
117818     
117819     /**
117820      * @cfg {String} text
117821      * The text/html to display in this item. Defaults to `undefined`.
117822      * @markdown
117823      */
117824     
117825     activate: function() {
117826         var me = this;
117827         
117828         if (!me.activated && me.canActivate && me.rendered && !me.isDisabled() && me.isVisible()) {
117829             me.el.addCls(me.activeCls);
117830             me.focus();
117831             me.activated = true;
117832             me.fireEvent('activate', me);
117833         }
117834     },
117835     
117836     blur: function() {
117837         this.$focused = false;
117838         this.callParent(arguments);
117839     },
117840     
117841     deactivate: function() {
117842         var me = this;
117843         
117844         if (me.activated) {
117845             me.el.removeCls(me.activeCls);
117846             me.blur();
117847             me.hideMenu();
117848             me.activated = false;
117849             me.fireEvent('deactivate', me);
117850         }
117851     },
117852     
117853     deferExpandMenu: function() {
117854         var me = this;
117855         
117856         if (!me.menu.rendered || !me.menu.isVisible()) {
117857             me.parentMenu.activeChild = me.menu;
117858             me.menu.parentItem = me;
117859             me.menu.parentMenu = me.menu.ownerCt = me.parentMenu;
117860             me.menu.showBy(me, me.menuAlign);
117861         }
117862     },
117863     
117864     deferHideMenu: function() {
117865         if (this.menu.isVisible()) {
117866             this.menu.hide();
117867         }
117868     },
117869     
117870     deferHideParentMenus: function() {
117871         Ext.menu.Manager.hideAll();
117872     },
117873     
117874     expandMenu: function(delay) {
117875         var me = this;
117876         
117877         if (me.menu) {
117878             clearTimeout(me.hideMenuTimer);
117879             if (delay === 0) {
117880                 me.deferExpandMenu();
117881             } else {
117882                 me.expandMenuTimer = Ext.defer(me.deferExpandMenu, Ext.isNumber(delay) ? delay : me.menuExpandDelay, me);
117883             }
117884         }
117885     },
117886     
117887     focus: function() {
117888         this.$focused = true;
117889         this.callParent(arguments);
117890     },
117891     
117892     getRefItems: function(deep){
117893         var menu = this.menu,
117894             items;
117895         
117896         if (menu) {
117897             items = menu.getRefItems(deep);
117898             items.unshift(menu);
117899         }   
117900         return items || [];   
117901     },
117902     
117903     hideMenu: function(delay) {
117904         var me = this;
117905         
117906         if (me.menu) {
117907             clearTimeout(me.expandMenuTimer);
117908             me.hideMenuTimer = Ext.defer(me.deferHideMenu, Ext.isNumber(delay) ? delay : me.menuHideDelay, me);
117909         }
117910     },
117911     
117912     initComponent: function() {
117913         var me = this,
117914             prefix = Ext.baseCSSPrefix,
117915             cls = [prefix + 'menu-item'];
117916         
117917         me.addEvents(
117918             /**
117919              * @event activate
117920              * Fires when this item is activated
117921              * @param {Ext.menu.Item} item The activated item
117922              */
117923             'activate',
117924             
117925             /**
117926              * @event click
117927              * Fires when this item is clicked
117928              * @param {Ext.menu.Item} item The item that was clicked
117929              * @param {Ext.EventObject} e The underyling {@link Ext.EventObject}.
117930              */
117931             'click',
117932             
117933             /**
117934              * @event deactivate
117935              * Fires when this tiem is deactivated
117936              * @param {Ext.menu.Item} item The deactivated item
117937              */
117938             'deactivate'
117939         );
117940         
117941         if (me.plain) {
117942             cls.push(prefix + 'menu-item-plain');
117943         }
117944         
117945         if (me.cls) {
117946             cls.push(me.cls);
117947         }
117948         
117949         me.cls = cls.join(' ');
117950         
117951         if (me.menu) {
117952             me.menu = Ext.menu.Manager.get(me.menu);
117953         }
117954         
117955         me.callParent(arguments);
117956     },
117957     
117958     onClick: function(e) {
117959         var me = this;
117960         
117961         if (!me.href) {
117962             e.stopEvent();
117963         }
117964         
117965         if (me.disabled) {
117966             return;
117967         }
117968         
117969         if (me.hideOnClick) {
117970             me.deferHideParentMenusTimer = Ext.defer(me.deferHideParentMenus, me.clickHideDelay, me);
117971         }
117972         
117973         Ext.callback(me.handler, me.scope || me, [me, e]);
117974         me.fireEvent('click', me, e);
117975         
117976         if (!me.hideOnClick) {
117977             me.focus();
117978         }
117979     },
117980     
117981     onDestroy: function() {
117982         var me = this;
117983         
117984         clearTimeout(me.expandMenuTimer);
117985         clearTimeout(me.hideMenuTimer);
117986         clearTimeout(me.deferHideParentMenusTimer);
117987         
117988         if (me.menu) {
117989             delete me.menu.parentItem;
117990             delete me.menu.parentMenu;
117991             delete me.menu.ownerCt;
117992             if (me.destroyMenu !== false) {
117993                 me.menu.destroy();
117994             }
117995         }
117996         me.callParent(arguments);
117997     },
117998     
117999     onRender: function(ct, pos) {
118000         var me = this,
118001             prefix = '.' + Ext.baseCSSPrefix;
118002         
118003         Ext.applyIf(me.renderData, {
118004             href: me.href || '#',
118005             hrefTarget: me.hrefTarget,
118006             icon: me.icon || Ext.BLANK_IMAGE_URL,
118007             iconCls: me.iconCls,
118008             menu: Ext.isDefined(me.menu),
118009             plain: me.plain,
118010             text: me.text
118011         });
118012         
118013         Ext.applyIf(me.renderSelectors, {
118014             itemEl: prefix + 'menu-item-link',
118015             iconEl: prefix + 'menu-item-icon',
118016             textEl: prefix + 'menu-item-text',
118017             arrowEl: prefix + 'menu-item-arrow'
118018         });
118019         
118020         me.callParent(arguments);
118021     },
118022     
118023     /**
118024      * Sets the {@link #click} handler of this item
118025      * @param {Function} fn The handler function
118026      * @param {Object} scope (optional) The scope of the handler function
118027      */
118028     setHandler: function(fn, scope) {
118029         this.handler = fn || null;
118030         this.scope = scope;
118031     },
118032     
118033     /**
118034      * Sets the {@link #iconCls} of this item
118035      * @param {String} iconCls The CSS class to set to {@link #iconCls}
118036      */
118037     setIconCls: function(iconCls) {
118038         var me = this;
118039         
118040         if (me.iconEl) {
118041             if (me.iconCls) {
118042                 me.iconEl.removeCls(me.iconCls);
118043             }
118044             
118045             if (iconCls) {
118046                 me.iconEl.addCls(iconCls);
118047             }
118048         }
118049         
118050         me.iconCls = iconCls;
118051     },
118052     
118053     /**
118054      * Sets the {@link #text} of this item
118055      * @param {String} text The {@link #text}
118056      */
118057     setText: function(text) {
118058         var me = this,
118059             el = me.textEl || me.el,
118060             newWidth;
118061         
118062         if (text && el) {
118063             el.update(text);
118064                 
118065             if (me.textEl) {
118066                 // Resize the menu to fit the text
118067                 newWidth = me.textEl.getWidth() + me.iconEl.getWidth() + 25 + (me.arrowEl ? me.arrowEl.getWidth() : 0);
118068                 if (newWidth > me.itemEl.getWidth()) {
118069                     me.parentMenu.setWidth(newWidth);
118070                 }
118071             }
118072         } else if (el) {
118073             el.update('');
118074         }
118075         
118076         me.text = text;
118077     }
118078 });
118079
118080 /**
118081  * @class Ext.menu.CheckItem
118082  * @extends Ext.menu.Item
118083
118084 A menu item that contains a togglable checkbox by default, but that can also be a part of a radio group.
118085 {@img Ext.menu.CheckItem/Ext.menu.CheckItem.png Ext.menu.CheckItem component}
118086 __Example Usage__    
118087     Ext.create('Ext.menu.Menu', {
118088                 width: 100,
118089                 height: 110,
118090                 floating: false,  // usually you want this set to True (default)
118091                 renderTo: Ext.getBody(),  // usually rendered by it's containing component
118092                 items: [{
118093                     xtype: 'menucheckitem',
118094                     text: 'select all'
118095                 },{
118096                     xtype: 'menucheckitem',
118097                         text: 'select specific',
118098                 },{
118099             iconCls: 'add16',
118100                     text: 'icon item' 
118101                 },{
118102                     text: 'regular item'
118103                 }]
118104         }); 
118105         
118106  * @xtype menucheckitem
118107  * @markdown
118108  * @constructor
118109  * @param {Object} config The config object
118110  */
118111
118112 Ext.define('Ext.menu.CheckItem', {
118113     extend: 'Ext.menu.Item',
118114     alias: 'widget.menucheckitem',
118115
118116     /**
118117      * @cfg {String} checkedCls
118118      * The CSS class used by {@link #cls} to show the checked state.
118119      * Defaults to `Ext.baseCSSPrefix + 'menu-item-checked'`.
118120      * @markdown
118121      */
118122     checkedCls: Ext.baseCSSPrefix + 'menu-item-checked',
118123     /**
118124      * @cfg {String} uncheckedCls
118125      * The CSS class used by {@link #cls} to show the unchecked state.
118126      * Defaults to `Ext.baseCSSPrefix + 'menu-item-unchecked'`.
118127      * @markdown
118128      */
118129     uncheckedCls: Ext.baseCSSPrefix + 'menu-item-unchecked',
118130     /**
118131      * @cfg {String} groupCls
118132      * The CSS class applied to this item's icon image to denote being a part of a radio group.
118133      * Defaults to `Ext.baseCSSClass + 'menu-group-icon'`.
118134      * Any specified {@link #iconCls} overrides this.
118135      * @markdown
118136      */
118137     groupCls: Ext.baseCSSPrefix + 'menu-group-icon',
118138
118139     /**
118140      * @cfg {Boolean} hideOnClick
118141      * Whether to not to hide the owning menu when this item is clicked.
118142      * Defaults to `false` for checkbox items, and to `true` for radio group items.
118143      * @markdown
118144      */
118145     hideOnClick: false,
118146
118147     afterRender: function() {
118148         var me = this;
118149         this.callParent();
118150         me.checked = !me.checked;
118151         me.setChecked(!me.checked, true);
118152     },
118153
118154     initComponent: function() {
118155         var me = this;
118156         me.addEvents(
118157             /**
118158              * @event beforecheckchange
118159              * Fires before a change event. Return false to cancel.
118160              * @param {Ext.menu.CheckItem} this
118161              * @param {Boolean} checked
118162              */
118163             'beforecheckchange',
118164
118165             /**
118166              * @event checkchange
118167              * Fires after a change event.
118168              * @param {Ext.menu.CheckItem} this
118169              * @param {Boolean} checked
118170              */
118171             'checkchange'
118172         );
118173
118174         me.callParent(arguments);
118175
118176         Ext.menu.Manager.registerCheckable(me);
118177
118178         if (me.group) {
118179             if (!me.iconCls) {
118180                 me.iconCls = me.groupCls;
118181             }
118182             if (me.initialConfig.hideOnClick !== false) {
118183                 me.hideOnClick = true;
118184             }
118185         }
118186     },
118187
118188     /**
118189      * Disables just the checkbox functionality of this menu Item. If this menu item has a submenu, that submenu
118190      * will still be accessible
118191      */
118192     disableCheckChange: function() {
118193         var me = this;
118194
118195         me.iconEl.addCls(me.disabledCls);
118196         me.checkChangeDisabled = true;
118197     },
118198
118199     /**
118200      * Reenables the checkbox functionality of this menu item after having been disabled by {@link #disableCheckChange}
118201      */
118202     enableCheckChange: function() {
118203         var me = this;
118204
118205         me.iconEl.removeCls(me.disabledCls);
118206         me.checkChangeDisabled = false;
118207     },
118208
118209     onClick: function(e) {
118210         var me = this;
118211         if(!me.disabled && !me.checkChangeDisabled && !(me.checked && me.group)) {
118212             me.setChecked(!me.checked);
118213         }
118214         this.callParent([e]);
118215     },
118216
118217     onDestroy: function() {
118218         Ext.menu.Manager.unregisterCheckable(this);
118219         this.callParent(arguments);
118220     },
118221
118222     /**
118223      * Sets the checked state of the item
118224      * @param {Boolean} checked True to check, false to uncheck
118225      * @param {Boolean} suppressEvents (optional) True to prevent firing the checkchange events. Defaults to `false`.
118226      * @markdown
118227      */
118228     setChecked: function(checked, suppressEvents) {
118229         var me = this;
118230         if (me.checked !== checked && (suppressEvents || me.fireEvent('beforecheckchange', me, checked) !== false)) {
118231             if (me.el) {
118232                 me.el[checked  ? 'addCls' : 'removeCls'](me.checkedCls)[!checked ? 'addCls' : 'removeCls'](me.uncheckedCls);
118233             }
118234             me.checked = checked;
118235             Ext.menu.Manager.onCheckChange(me, checked);
118236             if (!suppressEvents) {
118237                 Ext.callback(me.checkHandler, me.scope, [me, checked]);
118238                 me.fireEvent('checkchange', me, checked);
118239             }
118240         }
118241     }
118242 });
118243
118244 /**
118245  * @class Ext.menu.KeyNav
118246  * @private
118247  */
118248 Ext.define('Ext.menu.KeyNav', {
118249     extend: 'Ext.util.KeyNav',
118250
118251     requires: ['Ext.FocusManager'],
118252     
118253     constructor: function(menu) {
118254         var me = this;
118255
118256         me.menu = menu;
118257         me.callParent([menu.el, {
118258             down: me.down,
118259             enter: me.enter,
118260             esc: me.escape,
118261             left: me.left,
118262             right: me.right,
118263             space: me.enter,
118264             tab: me.tab,
118265             up: me.up
118266         }]);
118267     },
118268
118269     down: function(e) {
118270         var me = this,
118271             fi = me.menu.focusedItem;
118272
118273         if (fi && e.getKey() == Ext.EventObject.DOWN && me.isWhitelisted(fi)) {
118274             return true;
118275         }
118276         me.focusNextItem(1);
118277     },
118278
118279     enter: function(e) {
118280         var menu = this.menu;
118281
118282         if (menu.activeItem) {
118283             menu.onClick(e);
118284         }
118285     },
118286
118287     escape: function(e) {
118288         Ext.menu.Manager.hideAll();
118289     },
118290
118291     focusNextItem: function(step) {
118292         var menu = this.menu,
118293             items = menu.items,
118294             focusedItem = menu.focusedItem,
118295             startIdx = focusedItem ? items.indexOf(focusedItem) : -1,
118296             idx = startIdx + step;
118297
118298         while (idx != startIdx) {
118299             if (idx < 0) {
118300                 idx = items.length - 1;
118301             } else if (idx >= items.length) {
118302                 idx = 0;
118303             }
118304
118305             var item = items.getAt(idx);
118306             if (menu.canActivateItem(item)) {
118307                 menu.setActiveItem(item);
118308                 break;
118309             }
118310             idx += step;
118311         }
118312     },
118313
118314     isWhitelisted: function(item) {
118315         return Ext.FocusManager.isWhitelisted(item);
118316     },
118317
118318     left: function(e) {
118319         var menu = this.menu,
118320             fi = menu.focusedItem,
118321             ai = menu.activeItem;
118322
118323         if (fi && this.isWhitelisted(fi)) {
118324             return true;
118325         }
118326
118327         menu.hide();
118328         if (menu.parentMenu) {
118329             menu.parentMenu.focus();
118330         }
118331     },
118332
118333     right: function(e) {
118334         var menu = this.menu,
118335             fi = menu.focusedItem,
118336             ai = menu.activeItem,
118337             am;
118338
118339         if (fi && this.isWhitelisted(fi)) {
118340             return true;
118341         }
118342
118343         if (ai) {
118344             am = menu.activeItem.menu;
118345             if (am) {
118346                 ai.expandMenu(0);
118347                 Ext.defer(function() {
118348                     am.setActiveItem(am.items.getAt(0));
118349                 }, 25);
118350             }
118351         }
118352     },
118353
118354     tab: function(e) {
118355         var me = this;
118356
118357         if (e.shiftKey) {
118358             me.up(e);
118359         } else {
118360             me.down(e);
118361         }
118362     },
118363
118364     up: function(e) {
118365         var me = this,
118366             fi = me.menu.focusedItem;
118367
118368         if (fi && e.getKey() == Ext.EventObject.UP && me.isWhitelisted(fi)) {
118369             return true;
118370         }
118371         me.focusNextItem(-1);
118372     }
118373 });
118374 /**
118375  * @class Ext.menu.Separator
118376  * @extends Ext.menu.Item
118377  *
118378  * Adds a separator bar to a menu, used to divide logical groups of menu items. Generally you will
118379  * add one of these by using "-" in your call to add() or in your items config rather than creating one directly.
118380  *
118381  * {@img Ext.menu.Separator/Ext.menu.Separator.png Ext.menu.Separator component}
118382  *
118383  * ## Code 
118384  *
118385  *     Ext.create('Ext.menu.Menu', {
118386  *         width: 100,
118387  *         height: 100,
118388  *         floating: false,  // usually you want this set to True (default)
118389  *         renderTo: Ext.getBody(),  // usually rendered by it's containing component
118390  *         items: [{
118391  *             text: 'icon item',
118392  *             iconCls: 'add16'
118393  *         },{
118394  *             xtype: 'menuseparator'
118395  *         },{
118396  *            text: 'seperator above',
118397  *         },{
118398  *            text: 'regular item',
118399  *         }]
118400  *     }); 
118401  *
118402  * @xtype menuseparator
118403  * @markdown
118404  * @constructor
118405  * @param {Object} config The config object
118406  */
118407 Ext.define('Ext.menu.Separator', {
118408     extend: 'Ext.menu.Item',
118409     alias: 'widget.menuseparator',
118410     
118411     /**
118412      * @cfg {String} activeCls @hide
118413      */
118414     
118415     /**
118416      * @cfg {Boolean} canActivate @hide
118417      */
118418     canActivate: false,
118419     
118420     /**
118421      * @cfg {Boolean} clickHideDelay @hide
118422      */
118423      
118424     /**
118425      * @cfg {Boolean} destroyMenu @hide
118426      */
118427      
118428     /**
118429      * @cfg {Boolean} disabledCls @hide
118430      */
118431      
118432     focusable: false,
118433      
118434     /**
118435      * @cfg {String} href @hide
118436      */
118437     
118438     /**
118439      * @cfg {String} hrefTarget @hide
118440      */
118441     
118442     /**
118443      * @cfg {Boolean} hideOnClick @hide
118444      */
118445     hideOnClick: false,
118446     
118447     /**
118448      * @cfg {String} icon @hide
118449      */
118450     
118451     /**
118452      * @cfg {String} iconCls @hide
118453      */
118454     
118455     /**
118456      * @cfg {Mixed} menu @hide
118457      */
118458     
118459     /**
118460      * @cfg {String} menuAlign @hide
118461      */
118462     
118463     /**
118464      * @cfg {Number} menuExpandDelay @hide
118465      */
118466     
118467     /**
118468      * @cfg {Number} menuHideDelay @hide
118469      */
118470     
118471     /**
118472      * @cfg {Boolean} plain @hide
118473      */
118474     plain: true,
118475     
118476     /**
118477      * @cfg {String} separatorCls
118478      * The CSS class used by the separator item to show the incised line.
118479      * Defaults to `Ext.baseCSSPrefix + 'menu-item-separator'`.
118480      * @markdown
118481      */
118482     separatorCls: Ext.baseCSSPrefix + 'menu-item-separator',
118483     
118484     /**
118485      * @cfg {String} text @hide
118486      */
118487     text: '&#160;',
118488     
118489     onRender: function(ct, pos) {
118490         var me = this,
118491             sepCls = me.separatorCls;
118492             
118493         me.cls += ' ' + sepCls;
118494         
118495         Ext.applyIf(me.renderSelectors, {
118496             itemSepEl: '.' + sepCls
118497         });
118498         
118499         me.callParent(arguments);
118500     }
118501 });
118502 /**
118503  * @class Ext.menu.Menu
118504  * @extends Ext.panel.Panel
118505  *
118506  * A menu object. This is the container to which you may add {@link Ext.menu.Item menu items}.
118507  *
118508  * Menus may contain either {@link Ext.menu.Item menu items}, or general {@link Ext.Component Components}.
118509  * Menus may also contain {@link Ext.panel.AbstractPanel#dockedItems docked items} because it extends {@link Ext.panel.Panel}.
118510  *
118511  * To make a contained general {@link Ext.Component Component} line up with other {@link Ext.menu.Item menu items},
118512  * specify `{@link Ext.menu.Item#iconCls iconCls}: 'no-icon'` _or_ `{@link Ext.menu.Item#indent indent}: true`.
118513  * This reserves a space for an icon, and indents the Component in line with the other menu items.
118514  * See {@link Ext.form.field.ComboBox}.{@link Ext.form.field.ComboBox#getListParent getListParent} for an example.
118515
118516  * By default, Menus are absolutely positioned, floating Components. By configuring a Menu with `{@link #floating}:false`,
118517  * a Menu may be used as a child of a {@link Ext.container.Container Container}.
118518  * {@img Ext.menu.Item/Ext.menu.Item.png Ext.menu.Item component}
118519 __Example Usage__
118520         Ext.create('Ext.menu.Menu', {
118521                 width: 100,
118522                 height: 100,
118523                 margin: '0 0 10 0',
118524                 floating: false,  // usually you want this set to True (default)
118525                 renderTo: Ext.getBody(),  // usually rendered by it's containing component
118526                 items: [{                        
118527                         text: 'regular item 1'        
118528                 },{
118529                     text: 'regular item 2'
118530                 },{
118531                         text: 'regular item 3'  
118532                 }]
118533         }); 
118534         
118535         Ext.create('Ext.menu.Menu', {
118536                 width: 100,
118537                 height: 100,
118538                 plain: true,
118539                 floating: false,  // usually you want this set to True (default)
118540                 renderTo: Ext.getBody(),  // usually rendered by it's containing component
118541                 items: [{                        
118542                         text: 'plain item 1'    
118543                 },{
118544                     text: 'plain item 2'
118545                 },{
118546                         text: 'plain item 3'
118547                 }]
118548         }); 
118549  * @xtype menu
118550  * @markdown
118551  * @constructor
118552  * @param {Object} config The config object
118553  */
118554 Ext.define('Ext.menu.Menu', {
118555     extend: 'Ext.panel.Panel',
118556     alias: 'widget.menu',
118557     requires: [
118558         'Ext.layout.container.Fit',
118559         'Ext.layout.container.VBox',
118560         'Ext.menu.CheckItem',
118561         'Ext.menu.Item',
118562         'Ext.menu.KeyNav',
118563         'Ext.menu.Manager',
118564         'Ext.menu.Separator'
118565     ],
118566
118567     /**
118568      * @cfg {Boolean} allowOtherMenus
118569      * True to allow multiple menus to be displayed at the same time. Defaults to `false`.
118570      * @markdown
118571      */
118572     allowOtherMenus: false,
118573
118574     /**
118575      * @cfg {String} ariaRole @hide
118576      */
118577     ariaRole: 'menu',
118578
118579     /**
118580      * @cfg {Boolean} autoRender @hide
118581      * floating is true, so autoRender always happens
118582      */
118583
118584     /**
118585      * @cfg {String} defaultAlign
118586      * The default {@link Ext.core.Element#getAlignToXY Ext.core.Element#getAlignToXY} anchor position value for this menu
118587      * relative to its element of origin. Defaults to `'tl-bl?'`.
118588      * @markdown
118589      */
118590     defaultAlign: 'tl-bl?',
118591
118592     /**
118593      * @cfg {Boolean} floating
118594      * A Menu configured as `floating: true` (the default) will be rendered as an absolutely positioned,
118595      * {@link Ext.Component#floating floating} {@link Ext.Component Component}. If configured as `floating: false`, the Menu may be
118596      * used as a child item of another {@link Ext.container.Container Container}.
118597      * @markdown
118598      */
118599     floating: true,
118600
118601     /**
118602      * @cfg {Boolean} @hide
118603      * Menu performs its own size changing constraining, so ensure Component's constraining is not applied
118604      */
118605     constrain: false,
118606
118607     /**
118608      * @cfg {Boolean} hidden
118609      * True to initially render the Menu as hidden, requiring to be shown manually.
118610      * Defaults to `true` when `floating: true`, and defaults to `false` when `floating: false`.
118611      * @markdown
118612      */
118613     hidden: true,
118614
118615     /**
118616      * @cfg {Boolean} ignoreParentClicks
118617      * True to ignore clicks on any item in this menu that is a parent item (displays a submenu)
118618      * so that the submenu is not dismissed when clicking the parent item. Defaults to `false`.
118619      * @markdown
118620      */
118621     ignoreParentClicks: false,
118622
118623     isMenu: true,
118624
118625     /**
118626      * @cfg {String/Object} layout @hide
118627      */
118628
118629     /**
118630      * @cfg {Boolean} showSeparator True to show the icon separator. (defaults to true).
118631      */
118632     showSeparator : true,
118633
118634     /**
118635      * @cfg {Number} minWidth
118636      * The minimum width of the Menu. Defaults to `120`.
118637      * @markdown
118638      */
118639     minWidth: 120,
118640
118641     /**
118642      * @cfg {Boolean} plain
118643      * True to remove the incised line down the left side of the menu and to not
118644      * indent general Component items. Defaults to `false`.
118645      * @markdown
118646      */
118647
118648     initComponent: function() {
118649         var me = this,
118650             prefix = Ext.baseCSSPrefix;
118651
118652         me.addEvents(
118653             /**
118654              * @event click
118655              * Fires when this menu is clicked
118656              * @param {Ext.menu.Menu} menu The menu which has been clicked
118657              * @param {Ext.Component} item The menu item that was clicked. `undefined` if not applicable.
118658              * @param {Ext.EventObject} e The underlying {@link Ext.EventObject}.
118659              * @markdown
118660              */
118661             'click',
118662
118663             /**
118664              * @event mouseenter
118665              * Fires when the mouse enters this menu
118666              * @param {Ext.menu.Menu} menu The menu
118667              * @param {Ext.EventObject} e The underlying {@link Ext.EventObject}
118668              * @markdown
118669              */
118670             'mouseenter',
118671
118672             /**
118673              * @event mouseleave
118674              * Fires when the mouse leaves this menu
118675              * @param {Ext.menu.Menu} menu The menu
118676              * @param {Ext.EventObject} e The underlying {@link Ext.EventObject}
118677              * @markdown
118678              */
118679             'mouseleave',
118680
118681             /**
118682              * @event mouseover
118683              * Fires when the mouse is hovering over this menu
118684              * @param {Ext.menu.Menu} menu The menu
118685              * @param {Ext.Component} item The menu item that the mouse is over. `undefined` if not applicable.
118686              * @param {Ext.EventObject} e The underlying {@link Ext.EventObject}
118687              */
118688             'mouseover'
118689         );
118690
118691         Ext.menu.Manager.register(me);
118692
118693         // Menu classes
118694         var cls = [prefix + 'menu'];
118695         if (me.plain) {
118696             cls.push(prefix + 'menu-plain');
118697         }
118698         me.cls = cls.join(' ');
118699
118700         // Menu body classes
118701         var bodyCls = me.bodyCls ? [me.bodyCls] : [];
118702         bodyCls.unshift(prefix + 'menu-body');
118703         me.bodyCls = bodyCls.join(' ');
118704
118705         // Internal vbox layout, with scrolling overflow
118706         // Placed in initComponent (rather than prototype) in order to support dynamic layout/scroller
118707         // options if we wish to allow for such configurations on the Menu.
118708         // e.g., scrolling speed, vbox align stretch, etc.
118709         me.layout = {
118710             type: 'vbox',
118711             align: 'stretchmax',
118712             autoSize: true,
118713             clearInnerCtOnLayout: true,
118714             overflowHandler: 'Scroller'
118715         };
118716
118717         // hidden defaults to false if floating is configured as false
118718         if (me.floating === false && me.initialConfig.hidden !== true) {
118719             me.hidden = false;
118720         }
118721
118722         me.callParent(arguments);
118723
118724         me.on('beforeshow', function() {
118725             var hasItems = !!me.items.length;
118726             // FIXME: When a menu has its show cancelled because of no items, it
118727             // gets a visibility: hidden applied to it (instead of the default display: none)
118728             // Not sure why, but we remove this style when we want to show again.
118729             if (hasItems && me.rendered) {
118730                 me.el.setStyle('visibility', null);
118731             }
118732             return hasItems;
118733         });
118734     },
118735
118736     afterRender: function(ct) {
118737         var me = this,
118738             prefix = Ext.baseCSSPrefix,
118739             space = '&#160;';
118740
118741         me.callParent(arguments);
118742
118743         // TODO: Move this to a subTemplate When we support them in the future
118744         if (me.showSeparator) {
118745             me.iconSepEl = me.layout.getRenderTarget().insertFirst({
118746                 cls: prefix + 'menu-icon-separator',
118747                 html: space
118748             });
118749         }
118750
118751         me.focusEl = me.el.createChild({
118752             cls: prefix + 'menu-focus',
118753             tabIndex: '-1',
118754             html: space
118755         });
118756
118757         me.mon(me.el, {
118758             click: me.onClick,
118759             mouseover: me.onMouseOver,
118760             scope: me
118761         });
118762         me.mouseMonitor = me.el.monitorMouseLeave(100, me.onMouseLeave, me);
118763
118764         if (me.showSeparator && ((!Ext.isStrict && Ext.isIE) || Ext.isIE6)) {
118765             me.iconSepEl.setHeight(me.el.getHeight());
118766         }
118767
118768         me.keyNav = Ext.create('Ext.menu.KeyNav', me);
118769     },
118770
118771     afterLayout: function() {
118772         var me = this;
118773         me.callParent(arguments);
118774
118775         // For IE6 & IE quirks, we have to resize the el and body since position: absolute
118776         // floating elements inherit their parent's width, making them the width of
118777         // document.body instead of the width of their contents.
118778         // This includes left/right dock items.
118779         if ((!Ext.iStrict && Ext.isIE) || Ext.isIE6) {
118780             var innerCt = me.layout.getRenderTarget(),
118781                 innerCtWidth = 0,
118782                 dis = me.dockedItems,
118783                 l = dis.length,
118784                 i = 0,
118785                 di, clone, newWidth;
118786
118787             innerCtWidth = innerCt.getWidth();
118788
118789             newWidth = innerCtWidth + me.body.getBorderWidth('lr') + me.body.getPadding('lr');
118790
118791             // First set the body to the new width
118792             me.body.setWidth(newWidth);
118793
118794             // Now we calculate additional width (docked items) and set the el's width
118795             for (; i < l, di = dis.getAt(i); i++) {
118796                 if (di.dock == 'left' || di.dock == 'right') {
118797                     newWidth += di.getWidth();
118798                 }
118799             }
118800             me.el.setWidth(newWidth);
118801         }
118802     },
118803
118804     /**
118805      * Returns whether a menu item can be activated or not.
118806      * @return {Boolean}
118807      */
118808     canActivateItem: function(item) {
118809         return item && !item.isDisabled() && item.isVisible() && (item.canActivate || item.getXTypes().indexOf('menuitem') < 0);
118810     },
118811
118812     /**
118813      * Deactivates the current active item on the menu, if one exists.
118814      */
118815     deactivateActiveItem: function() {
118816         var me = this;
118817
118818         if (me.activeItem) {
118819             me.activeItem.deactivate();
118820             if (!me.activeItem.activated) {
118821                 delete me.activeItem;
118822             }
118823         }
118824         if (me.focusedItem) {
118825             me.focusedItem.blur();
118826             if (!me.focusedItem.$focused) {
118827                 delete me.focusedItem;
118828             }
118829         }
118830     },
118831
118832     // inherit docs
118833     getFocusEl: function() {
118834         return this.focusEl;
118835     },
118836
118837     // inherit docs
118838     hide: function() {
118839         this.deactivateActiveItem();
118840         this.callParent(arguments);
118841     },
118842
118843     // private
118844     getItemFromEvent: function(e) {
118845         return this.getChildByElement(e.getTarget());
118846     },
118847
118848     lookupComponent: function(cmp) {
118849         var me = this;
118850
118851         if (Ext.isString(cmp)) {
118852             cmp = me.lookupItemFromString(cmp);
118853         } else if (Ext.isObject(cmp)) {
118854             cmp = me.lookupItemFromObject(cmp);
118855         }
118856
118857         // Apply our minWidth to all of our child components so it's accounted
118858         // for in our VBox layout
118859         cmp.minWidth = cmp.minWidth || me.minWidth;
118860
118861         return cmp;
118862     },
118863
118864     // private
118865     lookupItemFromObject: function(cmp) {
118866         var me = this,
118867             prefix = Ext.baseCSSPrefix;
118868
118869         if (!cmp.isComponent) {
118870             if (!cmp.xtype) {
118871                 cmp = Ext.create('Ext.menu.' + (Ext.isBoolean(cmp.checked) ? 'Check': '') + 'Item', cmp);
118872             } else {
118873                 cmp = Ext.ComponentManager.create(cmp, cmp.xtype);
118874             }
118875         }
118876
118877         if (cmp.isMenuItem) {
118878             cmp.parentMenu = me;
118879         }
118880
118881         if (!cmp.isMenuItem && !cmp.dock) {
118882             var cls = [
118883                     prefix + 'menu-item',
118884                     prefix + 'menu-item-cmp'
118885                 ],
118886                 intercept = Ext.Function.createInterceptor;
118887
118888             // Wrap focus/blur to control component focus
118889             cmp.focus = intercept(cmp.focus, function() {
118890                 this.$focused = true;
118891             }, cmp);
118892             cmp.blur = intercept(cmp.blur, function() {
118893                 this.$focused = false;
118894             }, cmp);
118895
118896             if (!me.plain && (cmp.indent === true || cmp.iconCls === 'no-icon')) {
118897                 cls.push(prefix + 'menu-item-indent');
118898             }
118899
118900             if (cmp.rendered) {
118901                 cmp.el.addCls(cls);
118902             } else {
118903                 cmp.cls = (cmp.cls ? cmp.cls : '') + ' ' + cls.join(' ');
118904             }
118905             cmp.isMenuItem = true;
118906         }
118907         return cmp;
118908     },
118909
118910     // private
118911     lookupItemFromString: function(cmp) {
118912         return (cmp == 'separator' || cmp == '-') ?
118913             Ext.createWidget('menuseparator')
118914             : Ext.createWidget('menuitem', {
118915                 canActivate: false,
118916                 hideOnClick: false,
118917                 plain: true,
118918                 text: cmp
118919             });
118920     },
118921
118922     onClick: function(e) {
118923         var me = this,
118924             item;
118925
118926         if (me.disabled) {
118927             e.stopEvent();
118928             return;
118929         }
118930
118931         if ((e.getTarget() == me.focusEl.dom) || e.within(me.layout.getRenderTarget())) {
118932             item = me.getItemFromEvent(e) || me.activeItem;
118933
118934             if (item) {
118935                 if (item.getXTypes().indexOf('menuitem') >= 0) {
118936                     if (!item.menu || !me.ignoreParentClicks) {
118937                         item.onClick(e);
118938                     } else {
118939                         e.stopEvent();
118940                     }
118941                 }
118942             }
118943             me.fireEvent('click', me, item, e);
118944         }
118945     },
118946
118947     onDestroy: function() {
118948         var me = this;
118949
118950         Ext.menu.Manager.unregister(me);
118951         if (me.rendered) {
118952             me.el.un(me.mouseMonitor);
118953             me.keyNav.destroy();
118954             delete me.keyNav;
118955         }
118956         me.callParent(arguments);
118957     },
118958
118959     onMouseLeave: function(e) {
118960         var me = this;
118961
118962         me.deactivateActiveItem();
118963
118964         if (me.disabled) {
118965             return;
118966         }
118967
118968         me.fireEvent('mouseleave', me, e);
118969     },
118970
118971     onMouseOver: function(e) {
118972         var me = this,
118973             fromEl = e.getRelatedTarget(),
118974             mouseEnter = !me.el.contains(fromEl),
118975             item = me.getItemFromEvent(e);
118976
118977         if (mouseEnter && me.parentMenu) {
118978             me.parentMenu.setActiveItem(me.parentItem);
118979             me.parentMenu.mouseMonitor.mouseenter();
118980         }
118981
118982         if (me.disabled) {
118983             return;
118984         }
118985
118986         if (item) {
118987             me.setActiveItem(item);
118988             if (item.activated && item.expandMenu) {
118989                 item.expandMenu();
118990             }
118991         }
118992         if (mouseEnter) {
118993             me.fireEvent('mouseenter', me, e);
118994         }
118995         me.fireEvent('mouseover', me, item, e);
118996     },
118997
118998     setActiveItem: function(item) {
118999         var me = this;
119000
119001         if (item && (item != me.activeItem && item != me.focusedItem)) {
119002             me.deactivateActiveItem();
119003             if (me.canActivateItem(item)) {
119004                 if (item.activate) {
119005                     item.activate();
119006                     if (item.activated) {
119007                         me.activeItem = item;
119008                         me.focusedItem = item;
119009                         me.focus();
119010                     }
119011                 } else {
119012                     item.focus();
119013                     me.focusedItem = item;
119014                 }
119015             }
119016             item.el.scrollIntoView(me.layout.getRenderTarget());
119017         }
119018     },
119019
119020     /**
119021      * Shows the floating menu by the specified {@link Ext.Component Component} or {@link Ext.core.Element Element}.
119022      * @param {Mixed component} The {@link Ext.Component} or {@link Ext.core.Element} to show the menu by.
119023      * @param {String} position (optional) Alignment position as used by {@link Ext.core.Element#getAlignToXY Ext.core.Element.getAlignToXY}. Defaults to `{@link #defaultAlign}`.
119024      * @param {Array} offsets (optional) Alignment offsets as used by {@link Ext.core.Element#getAlignToXY Ext.core.Element.getAlignToXY}. Defaults to `undefined`.
119025      * @return {Menu} This Menu.
119026      * @markdown
119027      */
119028     showBy: function(cmp, pos, off) {
119029         var me = this;
119030
119031         if (me.floating && cmp) {
119032             me.layout.autoSize = true;
119033             me.show();
119034
119035             // Component or Element
119036             cmp = cmp.el || cmp;
119037
119038             // Convert absolute to floatParent-relative coordinates if necessary.
119039             var xy = me.el.getAlignToXY(cmp, pos || me.defaultAlign, off);
119040             if (me.floatParent) {
119041                 var r = me.floatParent.getTargetEl().getViewRegion();
119042                 xy[0] -= r.x;
119043                 xy[1] -= r.y;
119044             }
119045             me.showAt(xy);
119046             me.doConstrain();
119047         }
119048         return me;
119049     },
119050
119051     doConstrain : function() {
119052         var me = this,
119053             y = this.el.getY(),
119054             max, full,
119055             returnY = y, normalY, parentEl, scrollTop, viewHeight;
119056
119057         delete me.height;
119058         me.setSize();
119059         full = me.getHeight();
119060         if (me.floating) {
119061             parentEl = Ext.fly(me.el.dom.parentNode);
119062             scrollTop = parentEl.getScroll().top;
119063             viewHeight = parentEl.getViewSize().height;
119064             //Normalize y by the scroll position for the parent element.  Need to move it into the coordinate space
119065             //of the view.
119066             normalY = y - scrollTop;
119067             max = me.maxHeight ? me.maxHeight : viewHeight - normalY;
119068             if (full > viewHeight) {
119069                 max = viewHeight;
119070                 //Set returnY equal to (0,0) in view space by reducing y by the value of normalY
119071                 returnY = y - normalY;
119072             } else if (max < full) {
119073                 returnY = y - (full - max);
119074                 max = full;
119075             }
119076         }else{
119077             max = me.getHeight();
119078         }
119079         // Always respect maxHeight
119080         if (me.maxHeight){
119081             max = Math.min(me.maxHeight, max);
119082         }
119083         if (full > max && max > 0){
119084             me.layout.autoSize = false;
119085             me.setHeight(max);
119086             if (me.showSeparator){
119087                 me.iconSepEl.setHeight(me.layout.getRenderTarget().dom.scrollHeight);
119088             }
119089         }
119090         me.el.setY(returnY);
119091     }
119092 });
119093 /**
119094  * @class Ext.menu.ColorPicker
119095  * @extends Ext.menu.Menu
119096  * <p>A menu containing a {@link Ext.picker.Color} Component.</p>
119097  * <p>Notes:</p><div class="mdetail-params"><ul>
119098  * <li>Although not listed here, the <b>constructor</b> for this class
119099  * accepts all of the configuration options of <b>{@link Ext.picker.Color}</b>.</li>
119100  * <li>If subclassing ColorMenu, any configuration options for the ColorPicker must be
119101  * applied to the <tt><b>initialConfig</b></tt> property of the ColorMenu.
119102  * Applying {@link Ext.picker.Color ColorPicker} configuration settings to
119103  * <b><tt>this</tt></b> will <b>not</b> affect the ColorPicker's configuration.</li>
119104  * </ul></div>
119105  * {@img Ext.menu.ColorPicker/Ext.menu.ColorPicker.png Ext.menu.ColorPicker component}
119106  * __Example Usage__
119107      var colorPicker = Ext.create('Ext.menu.ColorPicker', {
119108         value: '000000'
119109     });
119110
119111     Ext.create('Ext.menu.Menu', {
119112                 width: 100,
119113                 height: 90,
119114                 floating: false,  // usually you want this set to True (default)
119115                 renderTo: Ext.getBody(),  // usually rendered by it's containing component
119116                 items: [{
119117                     text: 'choose a color',
119118                     menu: colorPicker
119119                 },{
119120             iconCls: 'add16',
119121                     text: 'icon item'
119122                 },{
119123                     text: 'regular item'
119124                 }]
119125         });
119126
119127  * @xtype colormenu
119128  * @author Nicolas Ferrero
119129  */
119130  Ext.define('Ext.menu.ColorPicker', {
119131      extend: 'Ext.menu.Menu',
119132
119133      alias: 'widget.colormenu',
119134
119135      requires: [
119136         'Ext.picker.Color'
119137      ],
119138
119139     /**
119140      * @cfg {Boolean} hideOnClick
119141      * False to continue showing the menu after a date is selected, defaults to true.
119142      */
119143     hideOnClick : true,
119144
119145     /**
119146      * @cfg {String} pickerId
119147      * An id to assign to the underlying color picker. Defaults to <tt>null</tt>.
119148      */
119149     pickerId : null,
119150
119151     /**
119152      * @cfg {Number} maxHeight
119153      * @hide
119154      */
119155
119156     /**
119157      * The {@link Ext.picker.Color} instance for this ColorMenu
119158      * @property picker
119159      * @type ColorPicker
119160      */
119161
119162     /**
119163      * @event click
119164      * @hide
119165      */
119166
119167     /**
119168      * @event itemclick
119169      * @hide
119170      */
119171
119172     initComponent : function(){
119173         var me = this;
119174
119175         Ext.apply(me, {
119176             plain: true,
119177             showSeparator: false,
119178             items: Ext.applyIf({
119179                 cls: Ext.baseCSSPrefix + 'menu-color-item',
119180                 id: me.pickerId,
119181                 xtype: 'colorpicker'
119182             }, me.initialConfig)
119183         });
119184
119185         me.callParent(arguments);
119186
119187         me.picker = me.down('colorpicker');
119188
119189         /**
119190          * @event select
119191          * Fires when a date is selected from the {@link #picker Ext.picker.Color}
119192          * @param {Ext.picker.Color} picker The {@link #picker Ext.picker.Color}
119193          * @param {String} color The 6-digit color hex code (without the # symbol)
119194          */
119195         me.relayEvents(me.picker, ['select']);
119196
119197         if (me.hideOnClick) {
119198             me.on('select', me.hidePickerOnSelect, me);
119199         }
119200     },
119201
119202     /**
119203      * Hides picker on select if hideOnClick is true
119204      * @private
119205      */
119206     hidePickerOnSelect: function() {
119207         Ext.menu.Manager.hideAll();
119208     }
119209  });
119210 /**
119211  * @class Ext.menu.DatePicker
119212  * @extends Ext.menu.Menu
119213  * <p>A menu containing an {@link Ext.picker.Date} Component.</p>
119214  * <p>Notes:</p><div class="mdetail-params"><ul>
119215  * <li>Although not listed here, the <b>constructor</b> for this class
119216  * accepts all of the configuration options of <b>{@link Ext.picker.Date}</b>.</li>
119217  * <li>If subclassing DateMenu, any configuration options for the DatePicker must be
119218  * applied to the <tt><b>initialConfig</b></tt> property of the DateMenu.
119219  * Applying {@link Ext.picker.Date DatePicker} configuration settings to
119220  * <b><tt>this</tt></b> will <b>not</b> affect the DatePicker's configuration.</li>
119221  * </ul></div>
119222  * {@img Ext.menu.DatePicker/Ext.menu.DatePicker.png Ext.menu.DatePicker component}
119223  * __Example Usage__
119224      var dateMenu = Ext.create('Ext.menu.DatePicker', {
119225         handler: function(dp, date){
119226             Ext.Msg.alert('Date Selected', 'You choose {0}.', Ext.Date.format(date, 'M j, Y'));
119227
119228         }
119229     });
119230
119231     Ext.create('Ext.menu.Menu', {
119232                 width: 100,
119233                 height: 90,
119234                 floating: false,  // usually you want this set to True (default)
119235                 renderTo: Ext.getBody(),  // usually rendered by it's containing component
119236                 items: [{
119237                     text: 'choose a date',
119238                     menu: dateMenu
119239                 },{
119240             iconCls: 'add16',
119241                     text: 'icon item'
119242                 },{
119243                     text: 'regular item'
119244                 }]
119245         });
119246
119247  * @xtype datemenu
119248  * @author Nicolas Ferrero
119249  */
119250  Ext.define('Ext.menu.DatePicker', {
119251      extend: 'Ext.menu.Menu',
119252
119253      alias: 'widget.datemenu',
119254
119255      requires: [
119256         'Ext.picker.Date'
119257      ],
119258
119259     /**
119260      * @cfg {Boolean} hideOnClick
119261      * False to continue showing the menu after a date is selected, defaults to true.
119262      */
119263     hideOnClick : true,
119264
119265     /**
119266      * @cfg {String} pickerId
119267      * An id to assign to the underlying date picker. Defaults to <tt>null</tt>.
119268      */
119269     pickerId : null,
119270
119271     /**
119272      * @cfg {Number} maxHeight
119273      * @hide
119274      */
119275
119276     /**
119277      * The {@link Ext.picker.Date} instance for this DateMenu
119278      * @property picker
119279      * @type Ext.picker.Date
119280      */
119281
119282     /**
119283      * @event click
119284      * @hide
119285      */
119286
119287     /**
119288      * @event itemclick
119289      * @hide
119290      */
119291
119292     initComponent : function(){
119293         var me = this;
119294
119295         Ext.apply(me, {
119296             showSeparator: false,
119297             plain: true,
119298             items: Ext.applyIf({
119299                 cls: Ext.baseCSSPrefix + 'menu-date-item',
119300                 id: me.pickerId,
119301                 xtype: 'datepicker'
119302             }, me.initialConfig)
119303         });
119304
119305         me.callParent(arguments);
119306
119307         me.picker = me.down('datepicker');
119308         /**
119309          * @event select
119310          * Fires when a date is selected from the {@link #picker Ext.picker.Date}
119311          * @param {Ext.picker.Date} picker The {@link #picker Ext.picker.Date}
119312          * @param {Date} date The selected date
119313          */
119314         me.relayEvents(me.picker, ['select']);
119315
119316         if (me.hideOnClick) {
119317             me.on('select', me.hidePickerOnSelect, me);
119318         }
119319     },
119320
119321     hidePickerOnSelect: function() {
119322         Ext.menu.Manager.hideAll();
119323     }
119324  });
119325 /**
119326  * @class Ext.panel.Tool
119327  * @extends Ext.Component
119328
119329 This class is used to display small visual icons in the header of a panel. There are a set of
119330 25 icons that can be specified by using the {@link #type} config. The {@link #handler} config
119331 can be used to provide a function that will respond to any click events. In general, this class
119332 will not be instantiated directly, rather it will be created by specifying the {@link Ext.panel.Panel#tools}
119333 configuration on the Panel itself.
119334
119335 __Example Usage__
119336
119337     Ext.create('Ext.panel.Panel', {
119338        width: 200,
119339        height: 200,
119340        renderTo: document.body,
119341        title: 'A Panel',
119342        tools: [{
119343            type: 'help',
119344            handler: function(){
119345                // show help here
119346            }
119347        }, {
119348            itemId: 'refresh',
119349            type: 'refresh',
119350            hidden: true,
119351            handler: function(){
119352                // do refresh
119353            }
119354        }, {
119355            type: 'search',
119356            handler: function(event, target, owner, tool){
119357                // do search
119358                owner.child('#refresh').show();
119359            }
119360        }]
119361     });
119362
119363  * @markdown
119364  * @xtype tool
119365  */
119366 Ext.define('Ext.panel.Tool', {
119367     extend: 'Ext.Component',
119368     requires: ['Ext.tip.QuickTipManager'],
119369     alias: 'widget.tool',
119370
119371     baseCls: Ext.baseCSSPrefix + 'tool',
119372     disabledCls: Ext.baseCSSPrefix + 'tool-disabled',
119373     toolPressedCls: Ext.baseCSSPrefix + 'tool-pressed',
119374     toolOverCls: Ext.baseCSSPrefix + 'tool-over',
119375     ariaRole: 'button',
119376     renderTpl: ['<img src="{blank}" class="{baseCls}-{type}" role="presentation"/>'],
119377     
119378     /**
119379      * @cfg {Function} handler
119380      * A function to execute when the tool is clicked.
119381      * Arguments passed are:
119382      * <ul>
119383      * <li><b>event</b> : Ext.EventObject<div class="sub-desc">The click event.</div></li>
119384      * <li><b>toolEl</b> : Ext.core.Element<div class="sub-desc">The tool Element.</div></li>
119385      * <li><b>panel</b> : Ext.panel.Panel<div class="sub-desc">The host Panel</div></li>
119386      * <li><b>tool</b> : Ext.panel.Tool<div class="sub-desc">The tool object</div></li>
119387      * </ul>
119388      */
119389     
119390     /**
119391      * @cfg {Object} scope
119392      * The scope to execute the {@link #handler} function. Defaults to the tool.
119393      */
119394     
119395     /**
119396      * @cfg {String} type
119397      * The type of tool to render. The following types are available:
119398      * <ul>
119399      * <li>close</li>
119400      * <li>collapse</li>
119401      * <li>down</li>
119402      * <li>expand</li>
119403      * <li>gear</li>
119404      * <li>help</li>
119405      * <li>left</li>
119406      * <li>maximize</li>
119407      * <li>minimize</li>
119408      * <li>minus</li>
119409      * <li>move</li>
119410      * <li>next</li>
119411      * <li>pin</li>
119412      * <li>plus</li>
119413      * <li>prev</li>
119414      * <li>print</li>
119415      * <li>refresh</li>
119416      * <li>resize</li>
119417      * <li>restore</li>
119418      * <li>right</li>
119419      * <li>save</li>
119420      * <li>search</li>
119421      * <li>toggle</li>
119422      * <li>unpin</li>
119423      * <li>up</li>
119424      * </ul>
119425      */
119426     
119427     /**
119428      * @cfg {String/Object} tooltip 
119429      * The tooltip for the tool - can be a string to be used as innerHTML (html tags are accepted) or QuickTips config object
119430      */
119431     
119432     /**
119433      * @cfg {Boolean} stopEvent
119434      * Defaults to true. Specify as false to allow click event to propagate.
119435      */
119436     stopEvent: true,
119437
119438     initComponent: function() {
119439         var me = this;
119440         me.addEvents(
119441             /**
119442              * @event click
119443              * Fires when the tool is clicked
119444              * @param {Ext.panel.Tool} this
119445              * @param {Ext.EventObject} e The event object
119446              */
119447             'click'
119448         );
119449         
119450         var types = [
119451             'close', 
119452             'collapse', 
119453             'down', 
119454             'expand', 
119455             'gear', 
119456             'help', 
119457             'left', 
119458             'maximize', 
119459             'minimize', 
119460             'minus', 
119461             'move', 
119462             'next', 
119463             'pin', 
119464             'plus', 
119465             'prev', 
119466             'print', 
119467             'refresh', 
119468             'resize', 
119469             'restore', 
119470             'right', 
119471             'save', 
119472             'search', 
119473             'toggle',
119474             'unpin', 
119475             'up'
119476         ];
119477         
119478         if (me.id && Ext.Array.indexOf(types, me.id) > -1) {
119479             Ext.global.console.warn('When specifying a tool you should use the type option, the id can conflict now that tool is a Component');
119480         }
119481         
119482         me.type = me.type || me.id;
119483
119484         Ext.applyIf(me.renderData, {
119485             baseCls: me.baseCls,
119486             blank: Ext.BLANK_IMAGE_URL,
119487             type: me.type
119488         });
119489         me.renderSelectors.toolEl = '.' + me.baseCls + '-' + me.type;
119490         me.callParent();
119491     },
119492
119493     // inherit docs
119494     afterRender: function() {
119495         var me = this;
119496         me.callParent(arguments);
119497         if (me.qtip) {
119498             if (Ext.isObject(me.qtip)) {
119499                 Ext.tip.QuickTipManager.register(Ext.apply({
119500                     target: me.id
119501                 }, me.qtip));
119502             }
119503             else {
119504                 me.toolEl.dom.qtip = me.qtip;
119505             }
119506         }
119507
119508         me.mon(me.toolEl, {
119509             click: me.onClick,
119510             mousedown: me.onMouseDown,
119511             mouseover: me.onMouseOver,
119512             mouseout: me.onMouseOut,
119513             scope: me
119514         });
119515     },
119516
119517     /**
119518      * Set the type of the tool. Allows the icon to be changed.
119519      * @param {String} type The new type. See the {@link #type} config.
119520      * @return {Ext.panel.Tool} this
119521      */
119522     setType: function(type) {
119523         var me = this;
119524         
119525         me.type = type;
119526         if (me.rendered) {
119527             me.toolEl.dom.className = me.baseCls + '-' + type;
119528         }
119529         return me;
119530     },
119531
119532     /**
119533      * Binds this tool to a component.
119534      * @private
119535      * @param {Ext.Component} component The component
119536      */
119537     bindTo: function(component) {
119538         this.owner = component;
119539     },
119540
119541     /**
119542      * Fired when the tool element is clicked
119543      * @private
119544      * @param {Ext.EventObject} e
119545      * @param {HTMLElement} target The target element
119546      */
119547     onClick: function(e, target) {
119548         var me = this,
119549             owner;
119550             
119551         if (me.disabled) {
119552             return false;
119553         }
119554         owner = me.owner || me.ownerCt;
119555
119556         //remove the pressed + over class
119557         me.el.removeCls(me.toolPressedCls);
119558         me.el.removeCls(me.toolOverCls);
119559
119560         if (me.stopEvent !== false) {
119561             e.stopEvent();
119562         }
119563
119564         Ext.callback(me.handler, me.scope || me, [e, target, owner, me]);
119565         me.fireEvent('click', me, e);
119566         return true;
119567     },
119568     
119569     // inherit docs
119570     onDestroy: function(){
119571         if (Ext.isObject(this.tooltip)) {
119572             Ext.tip.QuickTipManager.unregister(this.id);
119573         }    
119574         this.callParent();
119575     },
119576
119577     /**
119578      * Called then the user pressing their mouse button down on a tool
119579      * Adds the press class ({@link #toolPressedCls})
119580      * @private
119581      */
119582     onMouseDown: function() {
119583         if (this.disabled) {
119584             return false;
119585         }
119586
119587         this.el.addCls(this.toolPressedCls);
119588     },
119589
119590     /**
119591      * Called when the user rolls over a tool
119592      * Adds the over class ({@link #toolOverCls})
119593      * @private
119594      */
119595     onMouseOver: function() {
119596         if (this.disabled) {
119597             return false;
119598         }
119599         this.el.addCls(this.toolOverCls);
119600     },
119601
119602     /**
119603      * Called when the user rolls out from a tool.
119604      * Removes the over class ({@link #toolOverCls})
119605      * @private
119606      */
119607     onMouseOut: function() {
119608         this.el.removeCls(this.toolOverCls);
119609     }
119610 });
119611 /**
119612  * @class Ext.resizer.Handle
119613  * @extends Ext.Component
119614  *
119615  * Provides a handle for 9-point resizing of Elements or Components.
119616  */
119617 Ext.define('Ext.resizer.Handle', {
119618     extend: 'Ext.Component',
119619     handleCls: '',
119620     baseHandleCls: Ext.baseCSSPrefix + 'resizable-handle',
119621     // Ext.resizer.Resizer.prototype.possiblePositions define the regions
119622     // which will be passed in as a region configuration.
119623     region: '',
119624
119625     onRender: function() {
119626         this.addCls(
119627             this.baseHandleCls,
119628             this.baseHandleCls + '-' + this.region,
119629             this.handleCls
119630         );
119631         this.callParent(arguments);
119632         this.el.unselectable();
119633     }
119634 });
119635
119636 /**
119637  * @class Ext.resizer.Resizer
119638  * <p>Applies drag handles to an element or component to make it resizable. The
119639  * drag handles are inserted into the element (or component's element) and
119640  * positioned absolute.</p>
119641  *
119642  * <p>Textarea and img elements will be wrapped with an additional div because
119643  * these elements do not support child nodes. The original element can be accessed
119644  * through the originalTarget property.</p>
119645  *
119646  * <p>Here is the list of valid resize handles:</p>
119647  * <pre>
119648 Value   Description
119649 ------  -------------------
119650  'n'     north
119651  's'     south
119652  'e'     east
119653  'w'     west
119654  'nw'    northwest
119655  'sw'    southwest
119656  'se'    southeast
119657  'ne'    northeast
119658  'all'   all
119659 </pre>
119660  * {@img Ext.resizer.Resizer/Ext.resizer.Resizer.png Ext.resizer.Resizer component}
119661  * <p>Here's an example showing the creation of a typical Resizer:</p>
119662  * <pre><code>
119663     <div id="elToResize" style="width:200px; height:100px; background-color:#000000;"></div>
119664
119665     Ext.create('Ext.resizer.Resizer', {
119666         el: 'elToResize',
119667         handles: 'all',
119668         minWidth: 200,
119669         minHeight: 100,
119670         maxWidth: 500,
119671         maxHeight: 400,
119672         pinned: true
119673     });
119674 </code></pre>
119675 */
119676 Ext.define('Ext.resizer.Resizer', {
119677     mixins: {
119678         observable: 'Ext.util.Observable'
119679     },
119680     uses: ['Ext.resizer.ResizeTracker', 'Ext.Component'],
119681
119682     alternateClassName: 'Ext.Resizable',
119683
119684     handleCls: Ext.baseCSSPrefix + 'resizable-handle',
119685     pinnedCls: Ext.baseCSSPrefix + 'resizable-pinned',
119686     overCls:   Ext.baseCSSPrefix + 'resizable-over',
119687     proxyCls:  Ext.baseCSSPrefix + 'resizable-proxy',
119688     wrapCls:   Ext.baseCSSPrefix + 'resizable-wrap',
119689
119690     /**
119691      * @cfg {Boolean} dynamic
119692      * <p>Specify as true to update the {@link #target} (Element or {@link Ext.Component Component}) dynamically during dragging.
119693      * This is <code>true</code> by default, but the {@link Ext.Component Component} class passes <code>false</code> when it
119694      * is configured as {@link Ext.Component#resizable}.</p>
119695      * <p>If specified as <code>false</code>, a proxy element is displayed during the resize operation, and the {@link #target}
119696      * is updated on mouseup.</p>
119697      */
119698     dynamic: true,
119699
119700     /**
119701      * @cfg {String} handles String consisting of the resize handles to display. Defaults to 's e se' for
119702      * Elements and fixed position Components. Defaults to 8 point resizing for floating Components (such as Windows).
119703      * Specify either <code>'all'</code> or any of <code>'n s e w ne nw se sw'</code>.
119704      */
119705     handles: 's e se',
119706
119707     /**
119708      * @cfg {Number} height Optional. The height to set target to in pixels (defaults to null)
119709      */
119710     height : null,
119711
119712     /**
119713      * @cfg {Number} width Optional. The width to set the target to in pixels (defaults to null)
119714      */
119715     width : null,
119716
119717     /**
119718      * @cfg {Number} minHeight The minimum height for the element (defaults to 20)
119719      */
119720     minHeight : 20,
119721
119722     /**
119723      * @cfg {Number} minWidth The minimum width for the element (defaults to 20)
119724      */
119725     minWidth : 20,
119726
119727     /**
119728      * @cfg {Number} maxHeight The maximum height for the element (defaults to 10000)
119729      */
119730     maxHeight : 10000,
119731
119732     /**
119733      * @cfg {Number} maxWidth The maximum width for the element (defaults to 10000)
119734      */
119735     maxWidth : 10000,
119736
119737     /**
119738      * @cfg {Boolean} pinned True to ensure that the resize handles are always
119739      * visible, false indicates resizing by cursor changes only (defaults to false)
119740      */
119741     pinned: false,
119742
119743     /**
119744      * @cfg {Boolean} preserveRatio True to preserve the original ratio between height
119745      * and width during resize (defaults to false)
119746      */
119747     preserveRatio: false,
119748
119749     /**
119750      * @cfg {Boolean} transparent True for transparent handles. This is only applied at config time. (defaults to false)
119751      */
119752     transparent: false,
119753
119754     /**
119755      * @cfg {Mixed} constrainTo Optional. An element, or a {@link Ext.util.Region} into which the resize operation
119756      * must be constrained.
119757      */
119758
119759     possiblePositions: {
119760         n:  'north',
119761         s:  'south',
119762         e:  'east',
119763         w:  'west',
119764         se: 'southeast',
119765         sw: 'southwest',
119766         nw: 'northwest',
119767         ne: 'northeast'
119768     },
119769
119770     /**
119771      * @cfg {Mixed} target The Element or Component to resize.
119772      */
119773
119774     /**
119775      * Outer element for resizing behavior.
119776      * @type Ext.core.Element
119777      * @property el
119778      */
119779
119780     constructor: function(config) {
119781         var me = this,
119782             target,
119783             tag,
119784             handles = me.handles,
119785             handleCls,
119786             possibles,
119787             len,
119788             i = 0,
119789             pos;
119790
119791         this.addEvents(
119792             /**
119793              * @event beforeresize
119794              * Fired before resize is allowed. Return false to cancel resize.
119795              * @param {Ext.resizer.Resizer} this
119796              * @param {Number} width The start width
119797              * @param {Number} height The start height
119798              * @param {Ext.EventObject} e The mousedown event
119799              */
119800             'beforeresize',
119801             /**
119802              * @event resizedrag
119803              * Fires during resizing. Return false to cancel resize.
119804              * @param {Ext.resizer.Resizer} this
119805              * @param {Number} width The new width
119806              * @param {Number} height The new height
119807              * @param {Ext.EventObject} e The mousedown event
119808              */
119809             'resizedrag',
119810             /**
119811              * @event resize
119812              * Fired after a resize.
119813              * @param {Ext.resizer.Resizer} this
119814              * @param {Number} width The new width
119815              * @param {Number} height The new height
119816              * @param {Ext.EventObject} e The mouseup event
119817              */
119818             'resize'
119819         );
119820
119821         if (Ext.isString(config) || Ext.isElement(config) || config.dom) {
119822             target = config;
119823             config = arguments[1] || {};
119824             config.target = target;
119825         }
119826         // will apply config to this
119827         me.mixins.observable.constructor.call(me, config);
119828
119829         // If target is a Component, ensure that we pull the element out.
119830         // Resizer must examine the underlying Element.
119831         target = me.target;
119832         if (target) {
119833             if (target.isComponent) {
119834                 me.el = target.getEl();
119835                 if (target.minWidth) {
119836                     me.minWidth = target.minWidth;
119837                 }
119838                 if (target.minHeight) {
119839                     me.minHeight = target.minHeight;
119840                 }
119841                 if (target.maxWidth) {
119842                     me.maxWidth = target.maxWidth;
119843                 }
119844                 if (target.maxHeight) {
119845                     me.maxHeight = target.maxHeight;
119846                 }
119847                 if (target.floating) {
119848                     if (!this.hasOwnProperty('handles')) {
119849                         this.handles = 'n ne e se s sw w nw';
119850                     }
119851                 }
119852             } else {
119853                 me.el = me.target = Ext.get(target);
119854             }
119855         }
119856         // Backwards compatibility with Ext3.x's Resizable which used el as a config.
119857         else {
119858             me.target = me.el = Ext.get(me.el);
119859         }
119860
119861         // Tags like textarea and img cannot
119862         // have children and therefore must
119863         // be wrapped
119864         tag = me.el.dom.tagName;
119865         if (tag == 'TEXTAREA' || tag == 'IMG') {
119866             /**
119867              * Reference to the original resize target if the element of the original
119868              * resize target was an IMG or a TEXTAREA which must be wrapped in a DIV.
119869              * @type Mixed
119870              * @property originalTarget
119871              */
119872             me.originalTarget = me.target;
119873             me.target = me.el = me.el.wrap({
119874                 cls: me.wrapCls,
119875                 id: me.el.id + '-rzwrap'
119876             });
119877
119878             // Transfer originalTarget's positioning/sizing
119879             me.el.setPositioning(me.originalTarget.getPositioning());
119880             me.originalTarget.clearPositioning();
119881             var box = me.originalTarget.getBox();
119882             me.el.setBox(box);
119883         }
119884
119885         // Position the element, this enables us to absolute position
119886         // the handles within this.el
119887         me.el.position();
119888         if (me.pinned) {
119889             me.el.addCls(me.pinnedCls);
119890         }
119891
119892         /**
119893          * @type Ext.resizer.ResizeTracker
119894          * @property resizeTracker
119895          */
119896         me.resizeTracker = Ext.create('Ext.resizer.ResizeTracker', {
119897             disabled: me.disabled,
119898             target: me.target,
119899             constrainTo: me.constrainTo,
119900             overCls: me.overCls,
119901             throttle: me.throttle,
119902             originalTarget: me.originalTarget,
119903             delegate: '.' + me.handleCls,
119904             dynamic: me.dynamic,
119905             preserveRatio: me.preserveRatio,
119906             minHeight: me.minHeight,
119907             maxHeight: me.maxHeight,
119908             minWidth: me.minWidth,
119909             maxWidth: me.maxWidth
119910         });
119911
119912         // Relay the ResizeTracker's superclass events as our own resize events
119913         me.resizeTracker.on('mousedown', me.onBeforeResize, me);
119914         me.resizeTracker.on('drag', me.onResize, me);
119915         me.resizeTracker.on('dragend', me.onResizeEnd, me);
119916
119917         if (me.handles == 'all') {
119918             me.handles = 'n s e w ne nw se sw';
119919         }
119920
119921         handles = me.handles = me.handles.split(/ |\s*?[,;]\s*?/);
119922         possibles = me.possiblePositions;
119923         len = handles.length;
119924         handleCls = me.handleCls + ' ' + (this.target.isComponent ? (me.target.baseCls + '-handle ') : '') + me.handleCls + '-';
119925
119926         for(; i < len; i++){
119927             // if specified and possible, create
119928             if (handles[i] && possibles[handles[i]]) {
119929                 pos = possibles[handles[i]];
119930                 // store a reference in this.east, this.west, etc
119931
119932                 me[pos] = Ext.create('Ext.Component', {
119933                     owner: this,
119934                     region: pos,
119935                     cls: handleCls + pos,
119936                     renderTo: me.el
119937                 });
119938                 me[pos].el.unselectable();
119939                 if (me.transparent) {
119940                     me[pos].el.setOpacity(0);
119941                 }
119942             }
119943         }
119944
119945         // Constrain within configured maxima
119946         if (Ext.isNumber(me.width)) {
119947             me.width = Ext.Number.constrain(me.width, me.minWidth, me.maxWidth);
119948         }
119949         if (Ext.isNumber(me.height)) {
119950             me.height = Ext.Number.constrain(me.height, me.minHeight, me.maxHeight);
119951         }
119952
119953         // Size the element
119954         if (me.width != null || me.height != null) {
119955             if (me.originalTarget) {
119956                 me.originalTarget.setWidth(me.width);
119957                 me.originalTarget.setHeight(me.height);
119958             }
119959             me.resizeTo(me.width, me.height);
119960         }
119961
119962         me.forceHandlesHeight();
119963     },
119964
119965     disable: function() {
119966         this.resizeTracker.disable();
119967     },
119968
119969     enable: function() {
119970         this.resizeTracker.enable();
119971     },
119972
119973     /**
119974      * @private Relay the Tracker's mousedown event as beforeresize
119975      * @param tracker The Resizer
119976      * @param e The Event
119977      */
119978     onBeforeResize: function(tracker, e) {
119979         var b = this.target.getBox();
119980         return this.fireEvent('beforeresize', this, b.width, b.height, e);
119981     },
119982
119983     /**
119984      * @private Relay the Tracker's drag event as resizedrag
119985      * @param tracker The Resizer
119986      * @param e The Event
119987      */
119988     onResize: function(tracker, e) {
119989         var me = this,
119990             b = me.target.getBox();
119991         me.forceHandlesHeight();
119992         return me.fireEvent('resizedrag', me, b.width, b.height, e);
119993     },
119994
119995     /**
119996      * @private Relay the Tracker's dragend event as resize
119997      * @param tracker The Resizer
119998      * @param e The Event
119999      */
120000     onResizeEnd: function(tracker, e) {
120001         var me = this,
120002             b = me.target.getBox();
120003         me.forceHandlesHeight();
120004         return me.fireEvent('resize', me, b.width, b.height, e);
120005     },
120006
120007     /**
120008      * Perform a manual resize and fires the 'resize' event.
120009      * @param {Number} width
120010      * @param {Number} height
120011      */
120012     resizeTo : function(width, height){
120013         this.target.setSize(width, height);
120014         this.fireEvent('resize', this, width, height, null);
120015     },
120016
120017     /**
120018      * <p>Returns the element that was configured with the el or target config property.
120019      * If a component was configured with the target property then this will return the
120020      * element of this component.<p>
120021      * <p>Textarea and img elements will be wrapped with an additional div because
120022       * these elements do not support child nodes. The original element can be accessed
120023      * through the originalTarget property.</p>
120024      * @return {Element} element
120025      */
120026     getEl : function() {
120027         return this.el;
120028     },
120029
120030     /**
120031      * <p>Returns the element or component that was configured with the target config property.<p>
120032      * <p>Textarea and img elements will be wrapped with an additional div because
120033       * these elements do not support child nodes. The original element can be accessed
120034      * through the originalTarget property.</p>
120035      * @return {Element/Component}
120036      */
120037     getTarget: function() {
120038         return this.target;
120039     },
120040
120041     destroy: function() {
120042         var h;
120043         for (var i = 0, l = this.handles.length; i < l; i++) {
120044             h = this[this.possiblePositions[this.handles[i]]];
120045             delete h.owner;
120046             Ext.destroy(h);
120047         }
120048     },
120049
120050     /**
120051      * @private
120052      * Fix IE6 handle height issue.
120053      */
120054     forceHandlesHeight : function() {
120055         var me = this,
120056             handle;
120057         if (Ext.isIE6) {
120058             handle = me.east; 
120059             if (handle) {
120060                 handle.setHeight(me.el.getHeight());
120061             }
120062             handle = me.west; 
120063             if (handle) {
120064                 handle.setHeight(me.el.getHeight());
120065             }
120066             me.el.repaint();
120067         }
120068     }
120069 });
120070
120071 /**
120072  * @class Ext.resizer.ResizeTracker
120073  * @extends Ext.dd.DragTracker
120074  */
120075 Ext.define('Ext.resizer.ResizeTracker', {
120076     extend: 'Ext.dd.DragTracker',
120077     dynamic: true,
120078     preserveRatio: false,
120079
120080     // Default to no constraint
120081     constrainTo: null,
120082
120083     constructor: function(config) {
120084         var me = this;
120085
120086         if (!config.el) {
120087             if (config.target.isComponent) {
120088                 me.el = config.target.getEl();
120089             } else {
120090                 me.el = config.target;
120091             }
120092         }
120093         this.callParent(arguments);
120094
120095         // Ensure that if we are preserving aspect ratio, the largest minimum is honoured
120096         if (me.preserveRatio && me.minWidth && me.minHeight) {
120097             var widthRatio = me.minWidth / me.el.getWidth(),
120098                 heightRatio = me.minHeight / me.el.getHeight();
120099
120100             // largest ratio of minimum:size must be preserved.
120101             // So if a 400x200 pixel image has
120102             // minWidth: 50, maxWidth: 50, the maxWidth will be 400 * (50/200)... that is 100
120103             if (heightRatio > widthRatio) {
120104                 me.minWidth = me.el.getWidth() * heightRatio;
120105             } else {
120106                 me.minHeight = me.el.getHeight() * widthRatio;
120107             }
120108         }
120109
120110         // If configured as throttled, create an instance version of resize which calls
120111         // a throttled function to perform the resize operation.
120112         if (me.throttle) {
120113             var throttledResizeFn = Ext.Function.createThrottled(function() {
120114                     Ext.resizer.ResizeTracker.prototype.resize.apply(me, arguments);
120115                 }, me.throttle);
120116
120117             me.resize = function(box, direction, atEnd) {
120118                 if (atEnd) {
120119                     Ext.resizer.ResizeTracker.prototype.resize.apply(me, arguments);
120120                 } else {
120121                     throttledResizeFn.apply(null, arguments);
120122                 }
120123             };
120124         }
120125     },
120126
120127     onBeforeStart: function(e) {
120128         // record the startBox
120129         this.startBox = this.el.getBox();
120130     },
120131
120132     /**
120133      * @private
120134      * Returns the object that will be resized on every mousemove event.
120135      * If dynamic is false, this will be a proxy, otherwise it will be our actual target.
120136      */
120137     getDynamicTarget: function() {
120138         var d = this.target;
120139         if (this.dynamic) {
120140             return d;
120141         } else if (!this.proxy) {
120142             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());
120143             this.proxy.removeCls(Ext.baseCSSPrefix + 'proxy-el');
120144         }
120145         this.proxy.show();
120146         return this.proxy;
120147     },
120148
120149     onStart: function(e) {
120150         // returns the Ext.ResizeHandle that the user started dragging
120151         this.activeResizeHandle = Ext.getCmp(this.getDragTarget().id);
120152
120153         // If we are using a proxy, ensure it is sized.
120154         if (!this.dynamic) {
120155             this.resize(this.startBox, {
120156                 horizontal: 'none',
120157                 vertical: 'none'
120158             });
120159         }
120160     },
120161
120162     onDrag: function(e) {
120163         // dynamic resizing, update dimensions during resize
120164         if (this.dynamic || this.proxy) {
120165             this.updateDimensions(e);
120166         }
120167     },
120168
120169     updateDimensions: function(e, atEnd) {
120170         var me = this,
120171             region = me.activeResizeHandle.region,
120172             offset = me.getOffset(me.constrainTo ? 'dragTarget' : null),
120173             box = me.startBox,
120174             ratio,
120175             widthAdjust = 0,
120176             heightAdjust = 0,
120177             adjustX = 0,
120178             adjustY = 0,
120179             dragRatio,
120180             horizDir = offset[0] < 0 ? 'right' : 'left',
120181             vertDir = offset[1] < 0 ? 'down' : 'up',
120182             oppositeCorner,
120183             axis; // 1 = x, 2 = y, 3 = x and y.
120184
120185         switch (region) {
120186             case 'south':
120187                 heightAdjust = offset[1];
120188                 axis = 2;
120189                 break;
120190             case 'north':
120191                 heightAdjust = -offset[1];
120192                 adjustY = -heightAdjust;
120193                 axis = 2;
120194                 break;
120195             case 'east':
120196                 widthAdjust = offset[0];
120197                 axis = 1;
120198                 break;
120199             case 'west':
120200                 widthAdjust = -offset[0];
120201                 adjustX = -widthAdjust;
120202                 axis = 1;
120203                 break;
120204             case 'northeast':
120205                 heightAdjust = -offset[1];
120206                 adjustY = -heightAdjust;
120207                 widthAdjust = offset[0];
120208                 oppositeCorner = [box.x, box.y + box.height];
120209                 axis = 3;
120210                 break;
120211             case 'southeast':
120212                 heightAdjust = offset[1];
120213                 widthAdjust = offset[0];
120214                 oppositeCorner = [box.x, box.y];
120215                 axis = 3;
120216                 break;
120217             case 'southwest':
120218                 widthAdjust = -offset[0];
120219                 adjustX = -widthAdjust;
120220                 heightAdjust = offset[1];
120221                 oppositeCorner = [box.x + box.width, box.y];
120222                 axis = 3;
120223                 break;
120224             case 'northwest':
120225                 heightAdjust = -offset[1];
120226                 adjustY = -heightAdjust;
120227                 widthAdjust = -offset[0];
120228                 adjustX = -widthAdjust;
120229                 oppositeCorner = [box.x + box.width, box.y + box.height];
120230                 axis = 3;
120231                 break;
120232         }
120233
120234         var newBox = {
120235             width: box.width + widthAdjust,
120236             height: box.height + heightAdjust,
120237             x: box.x + adjustX,
120238             y: box.y + adjustY
120239         };
120240
120241         // out of bounds
120242         if (newBox.width < me.minWidth || newBox.width > me.maxWidth) {
120243             newBox.width = Ext.Number.constrain(newBox.width, me.minWidth, me.maxWidth);
120244             newBox.x = me.lastX || newBox.x;
120245         } else {
120246             me.lastX = newBox.x;
120247         }
120248         if (newBox.height < me.minHeight || newBox.height > me.maxHeight) {
120249             newBox.height = Ext.Number.constrain(newBox.height, me.minHeight, me.maxHeight);
120250             newBox.y = me.lastY || newBox.y;
120251         } else {
120252             me.lastY = newBox.y;
120253         }
120254
120255         // If this is configured to preserve the aspect ratio, or they are dragging using the shift key
120256         if (me.preserveRatio || e.shiftKey) {
120257             var newHeight,
120258                 newWidth;
120259
120260             ratio = me.startBox.width / me.startBox.height;
120261
120262             // Calculate aspect ratio constrained values.
120263             newHeight = Math.min(Math.max(me.minHeight, newBox.width / ratio), me.maxHeight);
120264             newWidth = Math.min(Math.max(me.minWidth, newBox.height * ratio), me.maxWidth);
120265
120266             // X axis: width-only change, height must obey
120267             if (axis == 1) {
120268                 newBox.height = newHeight;
120269             }
120270
120271             // Y axis: height-only change, width must obey
120272             else if (axis == 2) {
120273                 newBox.width = newWidth;
120274             }
120275
120276             // Corner drag.
120277             else {
120278                 // Drag ratio is the ratio of the mouse point from the opposite corner.
120279                 // Basically what edge we are dragging, a horizontal edge or a vertical edge.
120280                 dragRatio = Math.abs(oppositeCorner[0] - this.lastXY[0]) / Math.abs(oppositeCorner[1] - this.lastXY[1]);
120281
120282                 // If drag ratio > aspect ratio then width is dominant and height must obey
120283                 if (dragRatio > ratio) {
120284                     newBox.height = newHeight;
120285                 } else {
120286                     newBox.width = newWidth;
120287                 }
120288
120289                 // Handle dragging start coordinates
120290                 if (region == 'northeast') {
120291                     newBox.y = box.y - (newBox.height - box.height);
120292                 } else if (region == 'northwest') {
120293                     newBox.y = box.y - (newBox.height - box.height);
120294                     newBox.x = box.x - (newBox.width - box.width);
120295                 } else if (region == 'southwest') {
120296                     newBox.x = box.x - (newBox.width - box.width);
120297                 }
120298             }
120299         }
120300
120301         if (heightAdjust === 0) {
120302             vertDir = 'none';
120303         }
120304         if (widthAdjust === 0) {
120305             horizDir = 'none';
120306         }
120307         me.resize(newBox, {
120308             horizontal: horizDir,
120309             vertical: vertDir
120310         }, atEnd);
120311     },
120312
120313     getResizeTarget: function(atEnd) {
120314         return atEnd ? this.target : this.getDynamicTarget();
120315     },
120316
120317     resize: function(box, direction, atEnd) {
120318         var target = this.getResizeTarget(atEnd);
120319         if (target.isComponent) {
120320             if (target.floating) {
120321                 target.setPagePosition(box.x, box.y);
120322             }
120323             target.setSize(box.width, box.height);
120324         } else {
120325             target.setBox(box);
120326             // update the originalTarget if this was wrapped.
120327             if (this.originalTarget) {
120328                 this.originalTarget.setBox(box);
120329             }
120330         }
120331     },
120332
120333     onEnd: function(e) {
120334         this.updateDimensions(e, true);
120335         if (this.proxy) {
120336             this.proxy.hide();
120337         }
120338     }
120339 });
120340
120341 /**
120342  * @class Ext.resizer.SplitterTracker
120343  * @extends Ext.dd.DragTracker
120344  * Private utility class for Ext.Splitter.
120345  * @private
120346  */
120347 Ext.define('Ext.resizer.SplitterTracker', {
120348     extend: 'Ext.dd.DragTracker',
120349     requires: ['Ext.util.Region'],
120350     enabled: true,
120351
120352     getPrevCmp: function() {
120353         var splitter = this.getSplitter();
120354         return splitter.previousSibling();
120355     },
120356
120357     getNextCmp: function() {
120358         var splitter = this.getSplitter();
120359         return splitter.nextSibling();
120360     },
120361
120362     // ensure the tracker is enabled, store boxes of previous and next
120363     // components and calculate the constrain region
120364     onBeforeStart: function(e) {
120365         var prevCmp = this.getPrevCmp(),
120366             nextCmp = this.getNextCmp();
120367
120368         // SplitterTracker is disabled if any of its adjacents are collapsed.
120369         if (nextCmp.collapsed || prevCmp.collapsed) {
120370             return false;
120371         }
120372         // store boxes of previous and next
120373         this.prevBox  = prevCmp.getEl().getBox();
120374         this.nextBox  = nextCmp.getEl().getBox();
120375         this.constrainTo = this.calculateConstrainRegion();
120376     },
120377
120378     // We move the splitter el. Add the proxy class.
120379     onStart: function(e) {
120380         var splitter = this.getSplitter();
120381         splitter.addCls(splitter.baseCls + '-active');
120382     },
120383
120384     // calculate the constrain Region in which the splitter el may be moved.
120385     calculateConstrainRegion: function() {
120386         var splitter   = this.getSplitter(),
120387             topPad     = 0,
120388             bottomPad  = 0,
120389             splitWidth = splitter.getWidth(),
120390             defaultMin = splitter.defaultSplitMin,
120391             orient     = splitter.orientation,
120392             prevBox    = this.prevBox,
120393             prevCmp    = this.getPrevCmp(),
120394             nextBox    = this.nextBox,
120395             nextCmp    = this.getNextCmp(),
120396             // prev and nextConstrainRegions are the maximumBoxes minus the
120397             // minimumBoxes. The result is always the intersection
120398             // of these two boxes.
120399             prevConstrainRegion, nextConstrainRegion;
120400
120401         // vertical splitters, so resizing left to right
120402         if (orient === 'vertical') {
120403
120404             // Region constructor accepts (top, right, bottom, left)
120405             // anchored/calculated from the left
120406             prevConstrainRegion = Ext.create('Ext.util.Region',
120407                 prevBox.y,
120408                 // Right boundary is x + maxWidth if there IS a maxWidth.
120409                 // Otherwise it is calculated based upon the minWidth of the next Component
120410                 (prevCmp.maxWidth ? prevBox.x + prevCmp.maxWidth : nextBox.right - (nextCmp.minWidth || defaultMin)) + splitWidth,
120411                 prevBox.bottom,
120412                 prevBox.x + (prevCmp.minWidth || defaultMin)
120413             );
120414             // anchored/calculated from the right
120415             nextConstrainRegion = Ext.create('Ext.util.Region',
120416                 nextBox.y,
120417                 nextBox.right - (nextCmp.minWidth || defaultMin),
120418                 nextBox.bottom,
120419                 // Left boundary is right - maxWidth if there IS a maxWidth.
120420                 // Otherwise it is calculated based upon the minWidth of the previous Component
120421                 (nextCmp.maxWidth ? nextBox.right - nextCmp.maxWidth : prevBox.x + (prevBox.minWidth || defaultMin)) - splitWidth
120422             );
120423         } else {
120424             // anchored/calculated from the top
120425             prevConstrainRegion = Ext.create('Ext.util.Region',
120426                 prevBox.y + (prevCmp.minHeight || defaultMin),
120427                 prevBox.right,
120428                 // Bottom boundary is y + maxHeight if there IS a maxHeight.
120429                 // Otherwise it is calculated based upon the minWidth of the next Component
120430                 (prevCmp.maxHeight ? prevBox.y + prevCmp.maxHeight : nextBox.bottom - (nextCmp.minHeight || defaultMin)) + splitWidth,
120431                 prevBox.x
120432             );
120433             // anchored/calculated from the bottom
120434             nextConstrainRegion = Ext.create('Ext.util.Region',
120435                 // Top boundary is bottom - maxHeight if there IS a maxHeight.
120436                 // Otherwise it is calculated based upon the minHeight of the previous Component
120437                 (nextCmp.maxHeight ? nextBox.bottom - nextCmp.maxHeight : prevBox.y + (prevCmp.minHeight || defaultMin)) - splitWidth,
120438                 nextBox.right,
120439                 nextBox.bottom - (nextCmp.minHeight || defaultMin),
120440                 nextBox.x
120441             );
120442         }
120443
120444         // intersection of the two regions to provide region draggable
120445         return  prevConstrainRegion.intersect(nextConstrainRegion);
120446     },
120447
120448     // Performs the actual resizing of the previous and next components
120449     performResize: function(e) {
120450         var offset   = this.getOffset('dragTarget'),
120451             splitter = this.getSplitter(),
120452             orient   = splitter.orientation,
120453             prevCmp  = this.getPrevCmp(),
120454             nextCmp  = this.getNextCmp(),
120455             owner    = splitter.ownerCt,
120456             layout   = owner.getLayout();
120457
120458         // Inhibit automatic container layout caused by setSize calls below.
120459         owner.suspendLayout = true;
120460
120461         if (orient === 'vertical') {
120462             if (prevCmp) {
120463                 if (!prevCmp.maintainFlex) {
120464                     delete prevCmp.flex;
120465                     prevCmp.setSize(this.prevBox.width + offset[0], prevCmp.getHeight());
120466                 }
120467             }
120468             if (nextCmp) {
120469                 if (!nextCmp.maintainFlex) {
120470                     delete nextCmp.flex;
120471                     nextCmp.setSize(this.nextBox.width - offset[0], nextCmp.getHeight());
120472                 }
120473             }
120474         // verticals
120475         } else {
120476             if (prevCmp) {
120477                 if (!prevCmp.maintainFlex) {
120478                     delete prevCmp.flex;
120479                     prevCmp.setSize(prevCmp.getWidth(), this.prevBox.height + offset[1]);
120480                 }
120481             }
120482             if (nextCmp) {
120483                 if (!nextCmp.maintainFlex) {
120484                     delete nextCmp.flex;
120485                     nextCmp.setSize(prevCmp.getWidth(), this.nextBox.height - offset[1]);
120486                 }
120487             }
120488         }
120489         delete owner.suspendLayout;
120490         layout.onLayout();
120491     },
120492
120493     // perform the resize and remove the proxy class from the splitter el
120494     onEnd: function(e) {
120495         var splitter = this.getSplitter();
120496         splitter.removeCls(splitter.baseCls + '-active');
120497         this.performResize();
120498     },
120499
120500     // Track the proxy and set the proper XY coordinates
120501     // while constraining the drag
120502     onDrag: function(e) {
120503         var offset    = this.getOffset('dragTarget'),
120504             splitter  = this.getSplitter(),
120505             splitEl   = splitter.getEl(),
120506             orient    = splitter.orientation;
120507
120508         if (orient === "vertical") {
120509             splitEl.setX(this.startRegion.left + offset[0]);
120510         } else {
120511             splitEl.setY(this.startRegion.top + offset[1]);
120512         }
120513     },
120514
120515     getSplitter: function() {
120516         return Ext.getCmp(this.getDragCt().id);
120517     }
120518 });
120519 /**
120520  * @class Ext.selection.CellModel
120521  * @extends Ext.selection.Model
120522  * @private
120523  */
120524 Ext.define('Ext.selection.CellModel', {
120525     extend: 'Ext.selection.Model',
120526     alias: 'selection.cellmodel',
120527     requires: ['Ext.util.KeyNav'],
120528     
120529     /**
120530      * @cfg {Boolean} enableKeyNav
120531      * Turns on/off keyboard navigation within the grid. Defaults to true.
120532      */
120533     enableKeyNav: true,
120534     
120535     /**
120536      * @cfg {Boolean} preventWrap
120537      * Set this configuration to true to prevent wrapping around of selection as
120538      * a user navigates to the first or last column. Defaults to false.
120539      */
120540     preventWrap: false,
120541
120542     constructor: function(){
120543         this.addEvents(
120544             /**
120545              * @event deselect
120546              * Fired after a cell is deselected
120547              * @param {Ext.selection.CellModel} this
120548              * @param {Ext.data.Model} record The record of the deselected cell
120549              * @param {Number} row The row index deselected
120550              * @param {Number} column The column index deselected
120551              */
120552             'deselect',
120553             
120554             /**
120555              * @event select
120556              * Fired after a cell is selected
120557              * @param {Ext.selection.CellModel} this
120558              * @param {Ext.data.Model} record The record of the selected cell
120559              * @param {Number} row The row index selected
120560              * @param {Number} column The column index selected
120561              */
120562             'select'
120563         );
120564         this.callParent(arguments);    
120565     },
120566
120567     bindComponent: function(view) {
120568         var me = this;
120569         me.primaryView = view;
120570         me.views = me.views || [];
120571         me.views.push(view);
120572         me.bind(view.getStore(), true);
120573
120574         view.on({
120575             cellmousedown: me.onMouseDown,
120576             refresh: me.onViewRefresh,
120577             scope: me
120578         });
120579
120580         if (me.enableKeyNav) {
120581             me.initKeyNav(view);
120582         }
120583     },
120584
120585     initKeyNav: function(view) {
120586         var me = this;
120587         
120588         if (!view.rendered) {
120589             view.on('render', Ext.Function.bind(me.initKeyNav, me, [view], 0), me, {single: true});
120590             return;
120591         }
120592
120593         view.el.set({
120594             tabIndex: -1
120595         });
120596
120597         // view.el has tabIndex -1 to allow for
120598         // keyboard events to be passed to it.
120599         me.keyNav = Ext.create('Ext.util.KeyNav', view.el, {
120600             up: me.onKeyUp,
120601             down: me.onKeyDown,
120602             right: me.onKeyRight,
120603             left: me.onKeyLeft,
120604             tab: me.onKeyTab,
120605             scope: me
120606         });
120607     },
120608     
120609     getHeaderCt: function() {
120610         return this.primaryView.headerCt;
120611     },
120612
120613     onKeyUp: function(e, t) {
120614         this.move('up', e);
120615     },
120616
120617     onKeyDown: function(e, t) {
120618         this.move('down', e);
120619     },
120620
120621     onKeyLeft: function(e, t) {
120622         this.move('left', e);
120623     },
120624     
120625     onKeyRight: function(e, t) {
120626         this.move('right', e);
120627     },
120628     
120629     move: function(dir, e) {
120630         var me = this,
120631             pos = me.primaryView.walkCells(me.getCurrentPosition(), dir, e, me.preventWrap);
120632         if (pos) {
120633             me.setCurrentPosition(pos);
120634         }
120635         return pos;
120636     },
120637
120638     /**
120639      * Returns the current position in the format {row: row, column: column}
120640      */
120641     getCurrentPosition: function() {
120642         return this.position;
120643     },
120644     
120645     /**
120646      * Sets the current position
120647      * @param {Object} position The position to set.
120648      */
120649     setCurrentPosition: function(pos) {
120650         var me = this;
120651         
120652         if (me.position) {
120653             me.onCellDeselect(me.position);
120654         }
120655         if (pos) {
120656             me.onCellSelect(pos);
120657         }
120658         me.position = pos;
120659     },
120660
120661     /**
120662      * Set the current position based on where the user clicks.
120663      * @private
120664      */
120665     onMouseDown: function(view, cell, cellIndex, record, row, rowIndex, e) {
120666         this.setCurrentPosition({
120667             row: rowIndex,
120668             column: cellIndex
120669         });
120670     },
120671
120672     // notify the view that the cell has been selected to update the ui
120673     // appropriately and bring the cell into focus
120674     onCellSelect: function(position) {
120675         var me = this,
120676             store = me.view.getStore(),
120677             record = store.getAt(position.row);
120678
120679         me.doSelect(record);
120680         me.primaryView.onCellSelect(position);
120681         // TODO: Remove temporary cellFocus call here.
120682         me.primaryView.onCellFocus(position);
120683         me.fireEvent('select', me, record, position.row, position.column);
120684     },
120685
120686     // notify view that the cell has been deselected to update the ui
120687     // appropriately
120688     onCellDeselect: function(position) {
120689         var me = this,
120690             store = me.view.getStore(),
120691             record = store.getAt(position.row);
120692
120693         me.doDeselect(record);
120694         me.primaryView.onCellDeselect(position);
120695         me.fireEvent('deselect', me, record, position.row, position.column);
120696     },
120697
120698     onKeyTab: function(e, t) {
120699         var me = this,
120700             direction = e.shiftKey ? 'left' : 'right',
120701             editingPlugin = me.view.editingPlugin,
120702             position = me.move(direction, e);
120703
120704         if (editingPlugin && position && me.wasEditing) {
120705             editingPlugin.startEditByPosition(position);
120706         }
120707         delete me.wasEditing;
120708     },
120709
120710     onEditorTab: function(editingPlugin, e) {
120711         var me = this,
120712             direction = e.shiftKey ? 'left' : 'right',
120713             position  = me.move(direction, e);
120714
120715         if (position) {
120716             editingPlugin.startEditByPosition(position);
120717             me.wasEditing = true;
120718         }
120719     },
120720
120721     refresh: function() {
120722         var pos = this.getCurrentPosition();
120723         if (pos) {
120724             this.onCellSelect(pos);
120725         }
120726     },
120727
120728     onViewRefresh: function() {
120729         var pos = this.getCurrentPosition();
120730         if (pos) {
120731             this.onCellDeselect(pos);
120732             this.setCurrentPosition(null);
120733         }
120734     },
120735
120736     selectByPosition: function(position) {
120737         this.setCurrentPosition(position);
120738     }
120739 });
120740 /**
120741  * @class Ext.selection.RowModel
120742  * @extends Ext.selection.Model
120743  * 
120744  * Implement row based navigation via keyboard.
120745  *
120746  * Must synchronize across grid sections
120747  */
120748 Ext.define('Ext.selection.RowModel', {
120749     extend: 'Ext.selection.Model',
120750     alias: 'selection.rowmodel',
120751     requires: ['Ext.util.KeyNav'],
120752     
120753     /**
120754      * @private
120755      * Number of pixels to scroll to the left/right when pressing
120756      * left/right keys.
120757      */
120758     deltaScroll: 5,
120759     
120760     /**
120761      * @cfg {Boolean} enableKeyNav
120762      * 
120763      * Turns on/off keyboard navigation within the grid. Defaults to true.
120764      */
120765     enableKeyNav: true,
120766     
120767     constructor: function(){
120768         this.addEvents(
120769             /**
120770              * @event deselect
120771              * Fired after a record is deselected
120772              * @param {Ext.selection.RowSelectionModel} this
120773              * @param {Ext.data.Model} record The deselected record
120774              * @param {Number} index The row index deselected
120775              */
120776             'deselect',
120777             
120778             /**
120779              * @event select
120780              * Fired after a record is selected
120781              * @param {Ext.selection.RowSelectionModel} this
120782              * @param {Ext.data.Model} record The selected record
120783              * @param {Number} index The row index selected
120784              */
120785             'select'
120786         );
120787         this.callParent(arguments);    
120788     },
120789
120790     bindComponent: function(view) {
120791         var me = this;
120792         
120793         me.views = me.views || [];
120794         me.views.push(view);
120795         me.bind(view.getStore(), true);
120796
120797         view.on({
120798             itemmousedown: me.onRowMouseDown,
120799             scope: me
120800         });
120801
120802         if (me.enableKeyNav) {
120803             me.initKeyNav(view);
120804         }
120805     },
120806
120807     initKeyNav: function(view) {
120808         var me = this;
120809         
120810         if (!view.rendered) {
120811             view.on('render', Ext.Function.bind(me.initKeyNav, me, [view], 0), me, {single: true});
120812             return;
120813         }
120814
120815         view.el.set({
120816             tabIndex: -1
120817         });
120818
120819         // view.el has tabIndex -1 to allow for
120820         // keyboard events to be passed to it.
120821         me.keyNav = new Ext.util.KeyNav(view.el, {
120822             up: me.onKeyUp,
120823             down: me.onKeyDown,
120824             right: me.onKeyRight,
120825             left: me.onKeyLeft,
120826             pageDown: me.onKeyPageDown,
120827             pageUp: me.onKeyPageUp,
120828             home: me.onKeyHome,
120829             end: me.onKeyEnd,
120830             scope: me
120831         });
120832         view.el.on(Ext.EventManager.getKeyEvent(), me.onKeyPress, me);
120833     },
120834
120835     // Returns the number of rows currently visible on the screen or
120836     // false if there were no rows. This assumes that all rows are
120837     // of the same height and the first view is accurate.
120838     getRowsVisible: function() {
120839         var rowsVisible = false,
120840             view = this.views[0],
120841             row = view.getNode(0),
120842             rowHeight, gridViewHeight;
120843
120844         if (row) {
120845             rowHeight = Ext.fly(row).getHeight();
120846             gridViewHeight = view.el.getHeight();
120847             rowsVisible = Math.floor(gridViewHeight / rowHeight);
120848         }
120849
120850         return rowsVisible;
120851     },
120852
120853     // go to last visible record in grid.
120854     onKeyEnd: function(e, t) {
120855         var me = this,
120856             last = me.store.getAt(me.store.getCount() - 1);
120857             
120858         if (last) {
120859             if (e.shiftKey) {
120860                 me.selectRange(last, me.lastFocused || 0);
120861                 me.setLastFocused(last);
120862             } else if (e.ctrlKey) {
120863                 me.setLastFocused(last);
120864             } else {
120865                 me.doSelect(last);
120866             }
120867         }
120868     },
120869
120870     // go to first visible record in grid.
120871     onKeyHome: function(e, t) {
120872         var me = this,
120873             first = me.store.getAt(0);
120874             
120875         if (first) {
120876             if (e.shiftKey) {
120877                 me.selectRange(first, me.lastFocused || 0);
120878                 me.setLastFocused(first);
120879             } else if (e.ctrlKey) {
120880                 me.setLastFocused(first);
120881             } else {
120882                 me.doSelect(first, false);
120883             }
120884         }
120885     },
120886
120887     // Go one page up from the lastFocused record in the grid.
120888     onKeyPageUp: function(e, t) {
120889         var me = this,
120890             rowsVisible = me.getRowsVisible(),
120891             selIdx,
120892             prevIdx,
120893             prevRecord,
120894             currRec;
120895             
120896         if (rowsVisible) {
120897             selIdx = me.lastFocused ? me.store.indexOf(me.lastFocused) : 0;
120898             prevIdx = selIdx - rowsVisible;
120899             if (prevIdx < 0) {
120900                 prevIdx = 0;
120901             }
120902             prevRecord = me.store.getAt(prevIdx);
120903             if (e.shiftKey) {
120904                 currRec = me.store.getAt(selIdx);
120905                 me.selectRange(prevRecord, currRec, e.ctrlKey, 'up');
120906                 me.setLastFocused(prevRecord);
120907             } else if (e.ctrlKey) {
120908                 e.preventDefault();
120909                 me.setLastFocused(prevRecord);
120910             } else {
120911                 me.doSelect(prevRecord);
120912             }
120913
120914         }
120915     },
120916
120917     // Go one page down from the lastFocused record in the grid.
120918     onKeyPageDown: function(e, t) {
120919         var me = this,
120920             rowsVisible = me.getRowsVisible(),
120921             selIdx,
120922             nextIdx,
120923             nextRecord,
120924             currRec;
120925             
120926         if (rowsVisible) {
120927             selIdx = me.lastFocused ? me.store.indexOf(me.lastFocused) : 0;
120928             nextIdx = selIdx + rowsVisible;
120929             if (nextIdx >= me.store.getCount()) {
120930                 nextIdx = me.store.getCount() - 1;
120931             }
120932             nextRecord = me.store.getAt(nextIdx);
120933             if (e.shiftKey) {
120934                 currRec = me.store.getAt(selIdx);
120935                 me.selectRange(nextRecord, currRec, e.ctrlKey, 'down');
120936                 me.setLastFocused(nextRecord);
120937             } else if (e.ctrlKey) {
120938                 // some browsers, this means go thru browser tabs
120939                 // attempt to stop.
120940                 e.preventDefault();
120941                 me.setLastFocused(nextRecord);
120942             } else {
120943                 me.doSelect(nextRecord);
120944             }
120945         }
120946     },
120947
120948     // Select/Deselect based on pressing Spacebar.
120949     // Assumes a SIMPLE selectionmode style
120950     onKeyPress: function(e, t) {
120951         if (e.getKey() === e.SPACE) {
120952             e.stopEvent();
120953             var me = this,
120954                 record = me.lastFocused;
120955                 
120956             if (record) {
120957                 if (me.isSelected(record)) {
120958                     me.doDeselect(record, false);
120959                 } else {
120960                     me.doSelect(record, true);
120961                 }
120962             }
120963         }
120964     },
120965
120966     // Navigate one record up. This could be a selection or
120967     // could be simply focusing a record for discontiguous
120968     // selection. Provides bounds checking.
120969     onKeyUp: function(e, t) {
120970         var me = this,
120971             view = me.views[0],
120972             idx  = me.store.indexOf(me.lastFocused),
120973             record;
120974             
120975         if (idx > 0) {
120976             // needs to be the filtered count as thats what
120977             // will be visible.
120978             record = me.store.getAt(idx - 1);
120979             if (e.shiftKey && me.lastFocused) {
120980                 if (me.isSelected(me.lastFocused) && me.isSelected(record)) {
120981                     me.doDeselect(me.lastFocused, true);
120982                     me.setLastFocused(record);
120983                 } else if (!me.isSelected(me.lastFocused)) {
120984                     me.doSelect(me.lastFocused, true);
120985                     me.doSelect(record, true);
120986                 } else {
120987                     me.doSelect(record, true);
120988                 }
120989             } else if (e.ctrlKey) {
120990                 me.setLastFocused(record);
120991             } else {
120992                 me.doSelect(record);
120993                 //view.focusRow(idx - 1);
120994             }
120995         }
120996         // There was no lastFocused record, and the user has pressed up
120997         // Ignore??
120998         //else if (this.selected.getCount() == 0) {
120999         //    
121000         //    this.doSelect(record);
121001         //    //view.focusRow(idx - 1);
121002         //}
121003     },
121004
121005     // Navigate one record down. This could be a selection or
121006     // could be simply focusing a record for discontiguous
121007     // selection. Provides bounds checking.
121008     onKeyDown: function(e, t) {
121009         var me = this,
121010             view = me.views[0],
121011             idx  = me.store.indexOf(me.lastFocused),
121012             record;
121013             
121014         // needs to be the filtered count as thats what
121015         // will be visible.
121016         if (idx + 1 < me.store.getCount()) {
121017             record = me.store.getAt(idx + 1);
121018             if (me.selected.getCount() === 0) {
121019                 me.doSelect(record);
121020                 //view.focusRow(idx + 1);
121021             } else if (e.shiftKey && me.lastFocused) {
121022                 if (me.isSelected(me.lastFocused) && me.isSelected(record)) {
121023                     me.doDeselect(me.lastFocused, true);
121024                     me.setLastFocused(record);
121025                 } else if (!me.isSelected(me.lastFocused)) {
121026                     me.doSelect(me.lastFocused, true);
121027                     me.doSelect(record, true);
121028                 } else {
121029                     me.doSelect(record, true);
121030                 }
121031             } else if (e.ctrlKey) {
121032                 me.setLastFocused(record);
121033             } else {
121034                 me.doSelect(record);
121035                 //view.focusRow(idx + 1);
121036             }
121037         }
121038     },
121039     
121040     scrollByDeltaX: function(delta) {
121041         var view    = this.views[0],
121042             section = view.up(),
121043             hScroll = section.horizontalScroller;
121044             
121045         if (hScroll) {
121046             hScroll.scrollByDeltaX(delta);
121047         }
121048     },
121049     
121050     onKeyLeft: function(e, t) {
121051         this.scrollByDeltaX(-this.deltaScroll);
121052     },
121053     
121054     onKeyRight: function(e, t) {
121055         this.scrollByDeltaX(this.deltaScroll);
121056     },
121057
121058     // Select the record with the event included so that
121059     // we can take into account ctrlKey, shiftKey, etc
121060     onRowMouseDown: function(view, record, item, index, e) {
121061         view.el.focus();
121062         this.selectWithEvent(record, e);
121063     },
121064
121065     // Allow the GridView to update the UI by
121066     // adding/removing a CSS class from the row.
121067     onSelectChange: function(record, isSelected, suppressEvent) {
121068         var me      = this,
121069             views   = me.views,
121070             viewsLn = views.length,
121071             store   = me.store,
121072             rowIdx  = store.indexOf(record),
121073             i = 0;
121074             
121075         for (; i < viewsLn; i++) {
121076             if (isSelected) {
121077                 views[i].onRowSelect(rowIdx, suppressEvent);
121078                 if (!suppressEvent) {
121079                     me.fireEvent('select', me, record, rowIdx);
121080                 }
121081             } else {
121082                 views[i].onRowDeselect(rowIdx, suppressEvent);
121083                 if (!suppressEvent) {
121084                     me.fireEvent('deselect', me, record, rowIdx);
121085                 }
121086             }
121087         }
121088     },
121089
121090     // Provide indication of what row was last focused via
121091     // the gridview.
121092     onLastFocusChanged: function(oldFocused, newFocused, supressFocus) {
121093         var views   = this.views,
121094             viewsLn = views.length,
121095             store   = this.store,
121096             rowIdx,
121097             i = 0;
121098             
121099         if (oldFocused) {
121100             rowIdx = store.indexOf(oldFocused);
121101             if (rowIdx != -1) {
121102                 for (; i < viewsLn; i++) {
121103                     views[i].onRowFocus(rowIdx, false);
121104                 }
121105             }
121106         }
121107
121108         if (newFocused) {
121109             rowIdx = store.indexOf(newFocused);
121110             if (rowIdx != -1) {
121111                 for (i = 0; i < viewsLn; i++) {
121112                     views[i].onRowFocus(rowIdx, true, supressFocus);
121113                 }
121114             }
121115         }
121116     },
121117     
121118     onEditorTab: function(editingPlugin, e) {
121119         var me = this,
121120             view = me.views[0],
121121             record = editingPlugin.getActiveRecord(),
121122             header = editingPlugin.getActiveColumn(),
121123             position = view.getPosition(record, header),
121124             direction = e.shiftKey ? 'left' : 'right',
121125             newPosition  = view.walkCells(position, direction, e, this.preventWrap);
121126             
121127         if (newPosition) {
121128             editingPlugin.startEditByPosition(newPosition);
121129         }
121130     },
121131     
121132     selectByPosition: function(position) {
121133         var record = this.store.getAt(position.row);
121134         this.select(record);
121135     }
121136 });
121137 /**
121138  * @class Ext.selection.CheckboxModel
121139  * @extends Ext.selection.RowModel
121140  *
121141  * A selection model that renders a column of checkboxes that can be toggled to
121142  * select or deselect rows. The default mode for this selection model is MULTI.
121143  *
121144  * The selection model will inject a header for the checkboxes in the first view
121145  * and according to the 'injectCheckbox' configuration.
121146  */
121147 Ext.define('Ext.selection.CheckboxModel', {
121148     extend: 'Ext.selection.RowModel',
121149
121150     /**
121151      * @cfg {String} mode
121152      * Modes of selection.
121153      * Valid values are SINGLE, SIMPLE, and MULTI. Defaults to 'MULTI'
121154      */
121155     mode: 'MULTI',
121156
121157     /**
121158      * @cfg {Mixed} injectCheckbox
121159      * Instructs the SelectionModel whether or not to inject the checkbox header
121160      * automatically or not. (Note: By not placing the checkbox in manually, the
121161      * grid view will need to be rendered 2x on initial render.)
121162      * Supported values are a Number index, false and the strings 'first' and 'last'.
121163      * Default is 0.
121164      */
121165     injectCheckbox: 0,
121166
121167     /**
121168      * @cfg {Boolean} checkOnly <tt>true</tt> if rows can only be selected by clicking on the
121169      * checkbox column (defaults to <tt>false</tt>).
121170      */
121171     checkOnly: false,
121172
121173     // private
121174     checkerOnCls: Ext.baseCSSPrefix + 'grid-hd-checker-on',
121175
121176     bindComponent: function() {
121177         this.sortable = false;
121178         this.callParent(arguments);
121179
121180         var view     = this.views[0],
121181             headerCt = view.headerCt;
121182
121183         if (this.injectCheckbox !== false) {
121184             if (this.injectCheckbox == 'first') {
121185                 this.injectCheckbox = 0;
121186             } else if (this.injectCheckbox == 'last') {
121187                 this.injectCheckbox = headerCt.getColumnCount();
121188             }
121189             headerCt.add(this.injectCheckbox,  this.getHeaderConfig());
121190         }
121191         headerCt.on('headerclick', this.onHeaderClick, this);
121192     },
121193
121194     /**
121195      * Toggle the ui header between checked and unchecked state.
121196      * @param {Boolean} isChecked
121197      * @private
121198      */
121199     toggleUiHeader: function(isChecked) {
121200         var view     = this.views[0],
121201             headerCt = view.headerCt,
121202             checkHd  = headerCt.child('gridcolumn[isCheckerHd]');
121203
121204         if (checkHd) {
121205             if (isChecked) {
121206                 checkHd.el.addCls(this.checkerOnCls);
121207             } else {
121208                 checkHd.el.removeCls(this.checkerOnCls);
121209             }
121210         }
121211     },
121212
121213     /**
121214      * Toggle between selecting all and deselecting all when clicking on
121215      * a checkbox header.
121216      */
121217     onHeaderClick: function(headerCt, header, e) {
121218         if (header.isCheckerHd) {
121219             e.stopEvent();
121220             var isChecked = header.el.hasCls(Ext.baseCSSPrefix + 'grid-hd-checker-on');
121221             if (isChecked) {
121222                 // We have to supress the event or it will scrollTo the change
121223                 this.deselectAll(true);
121224             } else {
121225                 // We have to supress the event or it will scrollTo the change
121226                 this.selectAll(true);
121227             }
121228         }
121229     },
121230
121231     /**
121232      * Retrieve a configuration to be used in a HeaderContainer.
121233      * This should be used when injectCheckbox is set to false.
121234      */
121235     getHeaderConfig: function() {
121236         return {
121237             isCheckerHd: true,
121238             text : '&#160;',
121239             width: 24,
121240             sortable: false,
121241             fixed: true,
121242             hideable: false,
121243             menuDisabled: true,
121244             dataIndex: '',
121245             cls: Ext.baseCSSPrefix + 'column-header-checkbox ',
121246             renderer: Ext.Function.bind(this.renderer, this)
121247         };
121248     },
121249
121250     /**
121251      * Generates the HTML to be rendered in the injected checkbox column for each row.
121252      * Creates the standard checkbox markup by default; can be overridden to provide custom rendering.
121253      * See {@link Ext.grid.column.Column#renderer} for description of allowed parameters.
121254      */
121255     renderer: function(value, metaData, record, rowIndex, colIndex, store, view) {
121256         metaData.tdCls = Ext.baseCSSPrefix + 'grid-cell-special';
121257         return '<div class="' + Ext.baseCSSPrefix + 'grid-row-checker">&#160;</div>';
121258     },
121259
121260     // override
121261     onRowMouseDown: function(view, record, item, index, e) {
121262         view.el.focus();
121263         var me = this,
121264             checker = e.getTarget('.' + Ext.baseCSSPrefix + 'grid-row-checker');
121265
121266         // checkOnly set, but we didn't click on a checker.
121267         if (me.checkOnly && !checker) {
121268             return;
121269         }
121270
121271         if (checker) {
121272             var mode = me.getSelectionMode();
121273             // dont change the mode if its single otherwise
121274             // we would get multiple selection
121275             if (mode !== 'SINGLE') {
121276                 me.setSelectionMode('SIMPLE');
121277             }
121278             me.selectWithEvent(record, e);
121279             me.setSelectionMode(mode);
121280         } else {
121281             me.selectWithEvent(record, e);
121282         }
121283     },
121284
121285     /**
121286      * Synchronize header checker value as selection changes.
121287      * @private
121288      */
121289     onSelectChange: function(record, isSelected) {
121290         this.callParent([record, isSelected]);
121291         // check to see if all records are selected
121292         var hdSelectStatus = this.selected.getCount() === this.store.getCount();
121293         this.toggleUiHeader(hdSelectStatus);
121294     }
121295 });
121296
121297 /**
121298  * @class Ext.selection.TreeModel
121299  * @extends Ext.selection.RowModel
121300  *
121301  * Adds custom behavior for left/right keyboard navigation for use with a tree.
121302  * Depends on the view having an expand and collapse method which accepts a
121303  * record.
121304  * 
121305  * @private
121306  */
121307 Ext.define('Ext.selection.TreeModel', {
121308     extend: 'Ext.selection.RowModel',
121309     alias: 'selection.treemodel',
121310     
121311     // typically selection models prune records from the selection
121312     // model when they are removed, because the TreeView constantly
121313     // adds/removes records as they are expanded/collapsed
121314     pruneRemoved: false,
121315     
121316     onKeyRight: function(e, t) {
121317         var focused = this.getLastFocused(),
121318             view    = this.view;
121319             
121320         if (focused) {
121321             // tree node is already expanded, go down instead
121322             // this handles both the case where we navigate to firstChild and if
121323             // there are no children to the nextSibling
121324             if (focused.isExpanded()) {
121325                 this.onKeyDown(e, t);
121326             // if its not a leaf node, expand it
121327             } else if (!focused.isLeaf()) {
121328                 view.expand(focused);
121329             }
121330         }
121331     },
121332     
121333     onKeyLeft: function(e, t) {
121334         var focused = this.getLastFocused(),
121335             view    = this.view,
121336             viewSm  = view.getSelectionModel(),
121337             parentNode, parentRecord;
121338
121339         if (focused) {
121340             parentNode = focused.parentNode;
121341             // if focused node is already expanded, collapse it
121342             if (focused.isExpanded()) {
121343                 view.collapse(focused);
121344             // has a parentNode and its not root
121345             // TODO: this needs to cover the case where the root isVisible
121346             } else if (parentNode && !parentNode.isRoot()) {
121347                 // Select a range of records when doing multiple selection.
121348                 if (e.shiftKey) {
121349                     viewSm.selectRange(parentNode, focused, e.ctrlKey, 'up');
121350                     viewSm.setLastFocused(parentNode);
121351                 // just move focus, not selection
121352                 } else if (e.ctrlKey) {
121353                     viewSm.setLastFocused(parentNode);
121354                 // select it
121355                 } else {
121356                     viewSm.select(parentNode);
121357                 }
121358             }
121359         }
121360     },
121361     
121362     onKeyPress: function(e, t) {
121363         var selected, checked;
121364         
121365         if (e.getKey() === e.SPACE || e.getKey() === e.ENTER) {
121366             e.stopEvent();
121367             selected = this.getLastSelected();
121368             if (selected && selected.isLeaf()) {
121369                 checked = selected.get('checked');
121370                 if (Ext.isBoolean(checked)) {
121371                     selected.set('checked', !checked);
121372                 }
121373             }
121374         } else {
121375             this.callParent(arguments);
121376         }
121377     }
121378 });
121379
121380 /**
121381  * @private
121382  * @class Ext.slider.Thumb
121383  * @extends Ext.Base
121384  * @private
121385  * Represents a single thumb element on a Slider. This would not usually be created manually and would instead
121386  * be created internally by an {@link Ext.slider.Multi Ext.Slider}.
121387  */
121388 Ext.define('Ext.slider.Thumb', {
121389     requires: ['Ext.dd.DragTracker', 'Ext.util.Format'],
121390     /**
121391      * @private
121392      * @property topThumbZIndex
121393      * @type Number
121394      * The number used internally to set the z index of the top thumb (see promoteThumb for details)
121395      */
121396     topZIndex: 10000,
121397     /**
121398      * @constructor
121399      * @cfg {Ext.slider.MultiSlider} slider The Slider to render to (required)
121400      */
121401     constructor: function(config) {
121402         var me = this;
121403         
121404         /**
121405          * @property slider
121406          * @type Ext.slider.MultiSlider
121407          * The slider this thumb is contained within
121408          */
121409         Ext.apply(me, config || {}, {
121410             cls: Ext.baseCSSPrefix + 'slider-thumb',
121411
121412             /**
121413              * @cfg {Boolean} constrain True to constrain the thumb so that it cannot overlap its siblings
121414              */
121415             constrain: false
121416         });
121417         me.callParent([config]);
121418
121419         if (me.slider.vertical) {
121420             Ext.apply(me, Ext.slider.Thumb.Vertical);
121421         }
121422     },
121423
121424     /**
121425      * Renders the thumb into a slider
121426      */
121427     render: function() {
121428         var me = this;
121429         
121430         me.el = me.slider.innerEl.insertFirst({cls: me.cls});
121431         if (me.disabled) {
121432             me.disable();
121433         }
121434         me.initEvents();
121435     },
121436     
121437     /**
121438      * @private
121439      * move the thumb
121440      */
121441     move: function(v, animate){
121442         if(!animate){
121443             this.el.setLeft(v);
121444         }else{
121445             Ext.create('Ext.fx.Anim', {
121446                 target: this.el,
121447                 duration: 350,
121448                 to: {
121449                     left: v
121450                 }
121451             });
121452         }
121453     },
121454
121455     /**
121456      * @private
121457      * Bring thumb dom element to front.
121458      */
121459     bringToFront: function() {
121460         this.el.setStyle('zIndex', this.topZIndex);
121461     },
121462     
121463     /**
121464      * @private
121465      * Send thumb dom element to back.
121466      */
121467     sendToBack: function() {
121468         this.el.setStyle('zIndex', '');
121469     },
121470     
121471     /**
121472      * Enables the thumb if it is currently disabled
121473      */
121474     enable: function() {
121475         var me = this;
121476         
121477         me.disabled = false;
121478         if (me.el) {
121479             me.el.removeCls(me.slider.disabledCls);
121480         }
121481     },
121482
121483     /**
121484      * Disables the thumb if it is currently enabled
121485      */
121486     disable: function() {
121487         var me = this;
121488         
121489         me.disabled = true;
121490         if (me.el) {
121491             me.el.addCls(me.slider.disabledCls);
121492         }
121493     },
121494
121495     /**
121496      * Sets up an Ext.dd.DragTracker for this thumb
121497      */
121498     initEvents: function() {
121499         var me = this,
121500             el = me.el;
121501
121502         me.tracker = Ext.create('Ext.dd.DragTracker', {
121503             onBeforeStart: Ext.Function.bind(me.onBeforeDragStart, me),
121504             onStart      : Ext.Function.bind(me.onDragStart, me),
121505             onDrag       : Ext.Function.bind(me.onDrag, me),
121506             onEnd        : Ext.Function.bind(me.onDragEnd, me),
121507             tolerance    : 3,
121508             autoStart    : 300,
121509             overCls      : Ext.baseCSSPrefix + 'slider-thumb-over'
121510         });
121511
121512         me.tracker.initEl(el);
121513     },
121514
121515     /**
121516      * @private
121517      * This is tied into the internal Ext.dd.DragTracker. If the slider is currently disabled,
121518      * this returns false to disable the DragTracker too.
121519      * @return {Boolean} False if the slider is currently disabled
121520      */
121521     onBeforeDragStart : function(e) {
121522         if (this.disabled) {
121523             return false;
121524         } else {
121525             this.slider.promoteThumb(this);
121526             return true;
121527         }
121528     },
121529
121530     /**
121531      * @private
121532      * This is tied into the internal Ext.dd.DragTracker's onStart template method. Adds the drag CSS class
121533      * to the thumb and fires the 'dragstart' event
121534      */
121535     onDragStart: function(e){
121536         var me = this;
121537         
121538         me.el.addCls(Ext.baseCSSPrefix + 'slider-thumb-drag');
121539         me.dragging = true;
121540         me.dragStartValue = me.value;
121541
121542         me.slider.fireEvent('dragstart', me.slider, e, me);
121543     },
121544
121545     /**
121546      * @private
121547      * This is tied into the internal Ext.dd.DragTracker's onDrag template method. This is called every time
121548      * the DragTracker detects a drag movement. It updates the Slider's value using the position of the drag
121549      */
121550     onDrag: function(e) {
121551         var me       = this,
121552             slider   = me.slider,
121553             index    = me.index,
121554             newValue = me.getNewValue(),
121555             above,
121556             below;
121557
121558         if (me.constrain) {
121559             above = slider.thumbs[index + 1];
121560             below = slider.thumbs[index - 1];
121561
121562             if (below !== undefined && newValue <= below.value) {
121563                 newValue = below.value;
121564             }
121565             
121566             if (above !== undefined && newValue >= above.value) {
121567                 newValue = above.value;
121568             }
121569         }
121570
121571         slider.setValue(index, newValue, false);
121572         slider.fireEvent('drag', slider, e, me);
121573     },
121574
121575     getNewValue: function() {
121576         var slider = this.slider,
121577             pos = slider.innerEl.translatePoints(this.tracker.getXY());
121578
121579         return Ext.util.Format.round(slider.reverseValue(pos.left), slider.decimalPrecision);
121580     },
121581
121582     /**
121583      * @private
121584      * This is tied to the internal Ext.dd.DragTracker's onEnd template method. Removes the drag CSS class and
121585      * fires the 'changecomplete' event with the new value
121586      */
121587     onDragEnd: function(e) {
121588         var me     = this,
121589             slider = me.slider,
121590             value  = me.value;
121591
121592         me.el.removeCls(Ext.baseCSSPrefix + 'slider-thumb-drag');
121593
121594         me.dragging = false;
121595         slider.fireEvent('dragend', slider, e);
121596
121597         if (me.dragStartValue != value) {
121598             slider.fireEvent('changecomplete', slider, value, me);
121599         }
121600     },
121601
121602     destroy: function() {
121603         Ext.destroy(this.tracker);
121604     },
121605     statics: {
121606         // Method overrides to support vertical dragging of thumb within slider
121607         Vertical: {
121608             getNewValue: function() {
121609                 var slider   = this.slider,
121610                     innerEl  = slider.innerEl,
121611                     pos      = innerEl.translatePoints(this.tracker.getXY()),
121612                     bottom   = innerEl.getHeight() - pos.top;
121613
121614                 return Ext.util.Format.round(slider.reverseValue(bottom), slider.decimalPrecision);
121615             },
121616             move: function(v, animate) {
121617                 if (!animate) {
121618                     this.el.setBottom(v);
121619                 } else {
121620                     Ext.create('Ext.fx.Anim', {
121621                         target: this.el,
121622                         duration: 350,
121623                         to: {
121624                             bottom: v
121625                         }
121626                     });
121627                 }
121628             }
121629         }
121630     }
121631 });
121632
121633 /**
121634  * @class Ext.slider.Tip
121635  * @extends Ext.tip.Tip
121636  * Simple plugin for using an Ext.tip.Tip with a slider to show the slider value. In general this
121637  * class is not created directly, instead pass the {@link Ext.slider.Multi#useTips} and 
121638  * {@link Ext.slider.Multi#tipText} configuration options to the slider directly.
121639  * {@img Ext.slider.Tip/Ext.slider.Tip1.png Ext.slider.Tip component}
121640  * Example usage:
121641 <pre>
121642     Ext.create('Ext.slider.Single', {
121643         width: 214,
121644         minValue: 0,
121645         maxValue: 100,
121646         useTips: true,
121647         renderTo: Ext.getBody()
121648     });   
121649 </pre>
121650  * Optionally provide your own tip text by passing tipText:
121651  <pre>
121652  new Ext.slider.Single({
121653      width: 214,
121654      minValue: 0,
121655      maxValue: 100,
121656      useTips: true,
121657      tipText: function(thumb){
121658          return Ext.String.format('<b>{0}% complete</b>', thumb.value);
121659      }
121660  });
121661  </pre>
121662  * @xtype slidertip
121663  */
121664 Ext.define('Ext.slider.Tip', {
121665     extend: 'Ext.tip.Tip',
121666     minWidth: 10,
121667     alias: 'widget.slidertip',
121668     offsets : [0, -10],
121669     
121670     isSliderTip: true,
121671
121672     init: function(slider) {
121673         var me = this;
121674         
121675         slider.on({
121676             scope    : me,
121677             dragstart: me.onSlide,
121678             drag     : me.onSlide,
121679             dragend  : me.hide,
121680             destroy  : me.destroy
121681         });
121682     },
121683     /**
121684      * @private
121685      * Called whenever a dragstart or drag event is received on the associated Thumb. 
121686      * Aligns the Tip with the Thumb's new position.
121687      * @param {Ext.slider.MultiSlider} slider The slider
121688      * @param {Ext.EventObject} e The Event object
121689      * @param {Ext.slider.Thumb} thumb The thumb that the Tip is attached to
121690      */
121691     onSlide : function(slider, e, thumb) {
121692         var me = this;
121693         me.show();
121694         me.update(me.getText(thumb));
121695         me.doComponentLayout();
121696         me.el.alignTo(thumb.el, 'b-t?', me.offsets);
121697     },
121698
121699     /**
121700      * Used to create the text that appears in the Tip's body. By default this just returns
121701      * the value of the Slider Thumb that the Tip is attached to. Override to customize.
121702      * @param {Ext.slider.Thumb} thumb The Thumb that the Tip is attached to
121703      * @return {String} The text to display in the tip
121704      */
121705     getText : function(thumb) {
121706         return String(thumb.value);
121707     }
121708 });
121709 /**
121710  * @class Ext.slider.Multi
121711  * @extends Ext.form.field.Base
121712  * <p>Slider which supports vertical or horizontal orientation, keyboard adjustments, configurable snapping, axis
121713  * clicking and animation. Can be added as an item to any container. In addition,  
121714  * {@img Ext.slider.Multi/Ext.slider.Multi.png Ext.slider.Multi component}
121715  * <p>Example usage:</p>
121716  * Sliders can be created with more than one thumb handle by passing an array of values instead of a single one:
121717 <pre>
121718     Ext.create('Ext.slider.Multi', {
121719         width: 200,
121720         values: [25, 50, 75],
121721         increment: 5,
121722         minValue: 0,
121723         maxValue: 100,
121724
121725         //this defaults to true, setting to false allows the thumbs to pass each other
121726         {@link #constrainThumbs}: false,
121727         renderTo: Ext.getBody()
121728     });  
121729 </pre>
121730  * @xtype multislider
121731  */
121732 Ext.define('Ext.slider.Multi', {
121733     extend: 'Ext.form.field.Base',
121734     alias: 'widget.multislider',
121735     alternateClassName: 'Ext.slider.MultiSlider',
121736
121737     requires: [
121738         'Ext.slider.Thumb',
121739         'Ext.slider.Tip',
121740         'Ext.Number',
121741         'Ext.util.Format',
121742         'Ext.Template',
121743         'Ext.layout.component.field.Slider'
121744     ],
121745
121746     fieldSubTpl: [
121747         '<div class="' + Ext.baseCSSPrefix + 'slider {fieldCls} {vertical}" aria-valuemin="{minValue}" aria-valuemax="{maxValue}" aria-valuenow="{value}" aria-valuetext="{value}">',
121748             '<div class="' + Ext.baseCSSPrefix + 'slider-end" role="presentation">',
121749                 '<div class="' + Ext.baseCSSPrefix + 'slider-inner" role="presentation">',
121750                     '<a class="' + Ext.baseCSSPrefix + 'slider-focus" href="#" tabIndex="-1" hidefocus="on" role="presentation"></a>',
121751                 '</div>',
121752             '</div>',
121753         '</div>',
121754         {
121755             disableFormats: true,
121756             compiled: true
121757         }
121758     ],
121759
121760     /**
121761      * @cfg {Number} value
121762      * A value with which to initialize the slider. Defaults to minValue. Setting this will only
121763      * result in the creation of a single slider thumb; if you want multiple thumbs then use the
121764      * {@link #values} config instead.
121765      */
121766
121767     /**
121768      * @cfg {Array} values
121769      * Array of Number values with which to initalize the slider. A separate slider thumb will be created for
121770      * each value in this array. This will take precedence over the single {@link #value} config.
121771      */
121772
121773     /**
121774      * @cfg {Boolean} vertical Orient the Slider vertically rather than horizontally, defaults to false.
121775      */
121776     vertical: false,
121777     /**
121778      * @cfg {Number} minValue The minimum value for the Slider. Defaults to 0.
121779      */
121780     minValue: 0,
121781     /**
121782      * @cfg {Number} maxValue The maximum value for the Slider. Defaults to 100.
121783      */
121784     maxValue: 100,
121785     /**
121786      * @cfg {Number/Boolean} decimalPrecision.
121787      * <p>The number of decimal places to which to round the Slider's value. Defaults to 0.</p>
121788      * <p>To disable rounding, configure as <tt><b>false</b></tt>.</p>
121789      */
121790     decimalPrecision: 0,
121791     /**
121792      * @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.
121793      */
121794     keyIncrement: 1,
121795     /**
121796      * @cfg {Number} increment How many units to change the slider when adjusting by drag and drop. Use this option to enable 'snapping'.
121797      */
121798     increment: 0,
121799
121800     /**
121801      * @private
121802      * @property clickRange
121803      * @type Array
121804      * 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],
121805      * 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'
121806      * 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
121807      */
121808     clickRange: [5,15],
121809
121810     /**
121811      * @cfg {Boolean} clickToChange Determines whether or not clicking on the Slider axis will change the slider. Defaults to true
121812      */
121813     clickToChange : true,
121814     /**
121815      * @cfg {Boolean} animate Turn on or off animation. Defaults to true
121816      */
121817     animate: true,
121818
121819     /**
121820      * True while the thumb is in a drag operation
121821      * @type Boolean
121822      */
121823     dragging: false,
121824
121825     /**
121826      * @cfg {Boolean} constrainThumbs True to disallow thumbs from overlapping one another. Defaults to true
121827      */
121828     constrainThumbs: true,
121829
121830     componentLayout: 'sliderfield',
121831
121832     /**
121833      * @cfg {Boolean} useTips
121834      * True to use an Ext.slider.Tip to display tips for the value. Defaults to <tt>true</tt>.
121835      */
121836     useTips : true,
121837
121838     /**
121839      * @cfg {Function} tipText
121840      * A function used to display custom text for the slider tip. Defaults to <tt>null</tt>, which will
121841      * use the default on the plugin.
121842      */
121843     tipText : null,
121844
121845     ariaRole: 'slider',
121846
121847     // private override
121848     initValue: function() {
121849         var me = this,
121850             extValue = Ext.value,
121851             // Fallback for initial values: values config -> value config -> minValue config -> 0
121852             values = extValue(me.values, [extValue(me.value, extValue(me.minValue, 0))]),
121853             i = 0,
121854             len = values.length;
121855
121856         // Store for use in dirty check
121857         me.originalValue = values;
121858
121859         // Add a thumb for each value
121860         for (; i < len; i++) {
121861             me.addThumb(values[i]);
121862         }
121863     },
121864
121865     // private override
121866     initComponent : function() {
121867         var me = this,
121868             tipPlug,
121869             hasTip;
121870         
121871         /**
121872          * @property thumbs
121873          * @type Array
121874          * Array containing references to each thumb
121875          */
121876         me.thumbs = [];
121877
121878         me.keyIncrement = Math.max(me.increment, me.keyIncrement);
121879
121880         me.addEvents(
121881             /**
121882              * @event beforechange
121883              * Fires before the slider value is changed. By returning false from an event handler,
121884              * you can cancel the event and prevent the slider from changing.
121885              * @param {Ext.slider.Multi} slider The slider
121886              * @param {Number} newValue The new value which the slider is being changed to.
121887              * @param {Number} oldValue The old value which the slider was previously.
121888              */
121889             'beforechange',
121890
121891             /**
121892              * @event change
121893              * Fires when the slider value is changed.
121894              * @param {Ext.slider.Multi} slider The slider
121895              * @param {Number} newValue The new value which the slider has been changed to.
121896              * @param {Ext.slider.Thumb} thumb The thumb that was changed
121897              */
121898             'change',
121899
121900             /**
121901              * @event changecomplete
121902              * Fires when the slider value is changed by the user and any drag operations have completed.
121903              * @param {Ext.slider.Multi} slider The slider
121904              * @param {Number} newValue The new value which the slider has been changed to.
121905              * @param {Ext.slider.Thumb} thumb The thumb that was changed
121906              */
121907             'changecomplete',
121908
121909             /**
121910              * @event dragstart
121911              * Fires after a drag operation has started.
121912              * @param {Ext.slider.Multi} slider The slider
121913              * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
121914              */
121915             'dragstart',
121916
121917             /**
121918              * @event drag
121919              * Fires continuously during the drag operation while the mouse is moving.
121920              * @param {Ext.slider.Multi} slider The slider
121921              * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
121922              */
121923             'drag',
121924
121925             /**
121926              * @event dragend
121927              * Fires after the drag operation has completed.
121928              * @param {Ext.slider.Multi} slider The slider
121929              * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
121930              */
121931             'dragend'
121932         );
121933
121934         if (me.vertical) {
121935             Ext.apply(me, Ext.slider.Multi.Vertical);
121936         }
121937
121938         me.callParent();
121939
121940         // only can use it if it exists.
121941         if (me.useTips) {
121942             tipPlug = me.tipText ? {getText: me.tipText} : {};
121943             me.plugins = me.plugins || [];
121944             Ext.each(me.plugins, function(plug){
121945                 if (plug.isSliderTip) {
121946                     hasTip = true;
121947                     return false;
121948                 }
121949             });
121950             if (!hasTip) {
121951                 me.plugins.push(Ext.create('Ext.slider.Tip', tipPlug));
121952             }
121953         }
121954     },
121955
121956     /**
121957      * Creates a new thumb and adds it to the slider
121958      * @param {Number} value The initial value to set on the thumb. Defaults to 0
121959      * @return {Ext.slider.Thumb} The thumb
121960      */
121961     addThumb: function(value) {
121962         var me = this,
121963             thumb = Ext.create('Ext.slider.Thumb', {
121964             value    : value,
121965             slider   : me,
121966             index    : me.thumbs.length,
121967             constrain: me.constrainThumbs
121968         });
121969         me.thumbs.push(thumb);
121970
121971         //render the thumb now if needed
121972         if (me.rendered) {
121973             thumb.render();
121974         }
121975
121976         return thumb;
121977     },
121978
121979     /**
121980      * @private
121981      * Moves the given thumb above all other by increasing its z-index. This is called when as drag
121982      * any thumb, so that the thumb that was just dragged is always at the highest z-index. This is
121983      * required when the thumbs are stacked on top of each other at one of the ends of the slider's
121984      * range, which can result in the user not being able to move any of them.
121985      * @param {Ext.slider.Thumb} topThumb The thumb to move to the top
121986      */
121987     promoteThumb: function(topThumb) {
121988         var thumbs = this.thumbs,
121989             ln = thumbs.length,
121990             zIndex, thumb, i;
121991             
121992         for (i = 0; i < ln; i++) {
121993             thumb = thumbs[i];
121994
121995             if (thumb == topThumb) {
121996                 thumb.bringToFront();
121997             } else {
121998                 thumb.sendToBack();
121999             }
122000         }
122001     },
122002
122003     // private override
122004     onRender : function() {
122005         var me = this,
122006             i = 0,
122007             thumbs = me.thumbs,
122008             len = thumbs.length,
122009             thumb;
122010
122011         Ext.applyIf(me.subTplData, {
122012             vertical: me.vertical ? Ext.baseCSSPrefix + 'slider-vert' : Ext.baseCSSPrefix + 'slider-horz',
122013             minValue: me.minValue,
122014             maxValue: me.maxValue,
122015             value: me.value
122016         });
122017
122018         Ext.applyIf(me.renderSelectors, {
122019             endEl: '.' + Ext.baseCSSPrefix + 'slider-end',
122020             innerEl: '.' + Ext.baseCSSPrefix + 'slider-inner',
122021             focusEl: '.' + Ext.baseCSSPrefix + 'slider-focus'
122022         });
122023
122024         me.callParent(arguments);
122025
122026         //render each thumb
122027         for (; i < len; i++) {
122028             thumbs[i].render();
122029         }
122030
122031         //calculate the size of half a thumb
122032         thumb = me.innerEl.down('.' + Ext.baseCSSPrefix + 'slider-thumb');
122033         me.halfThumb = (me.vertical ? thumb.getHeight() : thumb.getWidth()) / 2;
122034
122035     },
122036
122037     /**
122038      * Utility method to set the value of the field when the slider changes.
122039      * @param {Object} slider The slider object.
122040      * @param {Object} v The new value.
122041      * @private
122042      */
122043     onChange : function(slider, v) {
122044         this.setValue(v, undefined, true);
122045     },
122046
122047     /**
122048      * @private
122049      * Adds keyboard and mouse listeners on this.el. Ignores click events on the internal focus element.
122050      */
122051     initEvents : function() {
122052         var me = this;
122053         
122054         me.mon(me.el, {
122055             scope    : me,
122056             mousedown: me.onMouseDown,
122057             keydown  : me.onKeyDown,
122058             change : me.onChange
122059         });
122060
122061         me.focusEl.swallowEvent("click", true);
122062     },
122063
122064     /**
122065      * @private
122066      * Mousedown handler for the slider. If the clickToChange is enabled and the click was not on the draggable 'thumb',
122067      * this calculates the new value of the slider and tells the implementation (Horizontal or Vertical) to move the thumb
122068      * @param {Ext.EventObject} e The click event
122069      */
122070     onMouseDown : function(e) {
122071         var me = this,
122072             thumbClicked = false,
122073             i = 0,
122074             thumbs = me.thumbs,
122075             len = thumbs.length,
122076             local;
122077             
122078         if (me.disabled) {
122079             return;
122080         }
122081
122082         //see if the click was on any of the thumbs
122083         for (; i < len; i++) {
122084             thumbClicked = thumbClicked || e.target == thumbs[i].el.dom;
122085         }
122086
122087         if (me.clickToChange && !thumbClicked) {
122088             local = me.innerEl.translatePoints(e.getXY());
122089             me.onClickChange(local);
122090         }
122091         me.focus();
122092     },
122093
122094     /**
122095      * @private
122096      * Moves the thumb to the indicated position. Note that a Vertical implementation is provided in Ext.slider.Multi.Vertical.
122097      * Only changes the value if the click was within this.clickRange.
122098      * @param {Object} local Object containing top and left values for the click event.
122099      */
122100     onClickChange : function(local) {
122101         var me = this,
122102             thumb, index;
122103             
122104         if (local.top > me.clickRange[0] && local.top < me.clickRange[1]) {
122105             //find the nearest thumb to the click event
122106             thumb = me.getNearest(local, 'left');
122107             if (!thumb.disabled) {
122108                 index = thumb.index;
122109                 me.setValue(index, Ext.util.Format.round(me.reverseValue(local.left), me.decimalPrecision), undefined, true);
122110             }
122111         }
122112     },
122113
122114     /**
122115      * @private
122116      * Returns the nearest thumb to a click event, along with its distance
122117      * @param {Object} local Object containing top and left values from a click event
122118      * @param {String} prop The property of local to compare on. Use 'left' for horizontal sliders, 'top' for vertical ones
122119      * @return {Object} The closest thumb object and its distance from the click event
122120      */
122121     getNearest: function(local, prop) {
122122         var me = this,
122123             localValue = prop == 'top' ? me.innerEl.getHeight() - local[prop] : local[prop],
122124             clickValue = me.reverseValue(localValue),
122125             nearestDistance = (me.maxValue - me.minValue) + 5, //add a small fudge for the end of the slider
122126             index = 0,
122127             nearest = null,
122128             thumbs = me.thumbs,
122129             i = 0,
122130             len = thumbs.length,
122131             thumb,
122132             value,
122133             dist;
122134
122135         for (; i < len; i++) {
122136             thumb = me.thumbs[i];
122137             value = thumb.value;
122138             dist  = Math.abs(value - clickValue);
122139
122140             if (Math.abs(dist <= nearestDistance)) {
122141                 nearest = thumb;
122142                 index = i;
122143                 nearestDistance = dist;
122144             }
122145         }
122146         return nearest;
122147     },
122148
122149     /**
122150      * @private
122151      * Handler for any keypresses captured by the slider. If the key is UP or RIGHT, the thumb is moved along to the right
122152      * by this.keyIncrement. If DOWN or LEFT it is moved left. Pressing CTRL moves the slider to the end in either direction
122153      * @param {Ext.EventObject} e The Event object
122154      */
122155     onKeyDown : function(e) {
122156         /*
122157          * The behaviour for keyboard handling with multiple thumbs is currently undefined.
122158          * There's no real sane default for it, so leave it like this until we come up
122159          * with a better way of doing it.
122160          */
122161         var me = this,
122162             k,
122163             val;
122164         
122165         if(me.disabled || me.thumbs.length !== 1) {
122166             e.preventDefault();
122167             return;
122168         }
122169         k = e.getKey();
122170         
122171         switch(k) {
122172             case e.UP:
122173             case e.RIGHT:
122174                 e.stopEvent();
122175                 val = e.ctrlKey ? me.maxValue : me.getValue(0) + me.keyIncrement;
122176                 me.setValue(0, val, undefined, true);
122177             break;
122178             case e.DOWN:
122179             case e.LEFT:
122180                 e.stopEvent();
122181                 val = e.ctrlKey ? me.minValue : me.getValue(0) - me.keyIncrement;
122182                 me.setValue(0, val, undefined, true);
122183             break;
122184             default:
122185                 e.preventDefault();
122186         }
122187     },
122188
122189     /**
122190      * @private
122191      * If using snapping, this takes a desired new value and returns the closest snapped
122192      * value to it
122193      * @param {Number} value The unsnapped value
122194      * @return {Number} The value of the nearest snap target
122195      */
122196     doSnap : function(value) {
122197         var newValue = value,
122198             inc = this.increment,
122199             m;
122200             
122201         if (!(inc && value)) {
122202             return value;
122203         }
122204         m = value % inc;
122205         if (m !== 0) {
122206             newValue -= m;
122207             if (m * 2 >= inc) {
122208                 newValue += inc;
122209             } else if (m * 2 < -inc) {
122210                 newValue -= inc;
122211             }
122212         }
122213         return Ext.Number.constrain(newValue, this.minValue,  this.maxValue);
122214     },
122215
122216     // private
122217     afterRender : function() {
122218         var me = this,
122219             i = 0,
122220             thumbs = me.thumbs,
122221             len = thumbs.length,
122222             thumb,
122223             v;
122224             
122225         me.callParent(arguments);
122226
122227         for (; i < len; i++) {
122228             thumb = thumbs[i];
122229
122230             if (thumb.value !== undefined) {
122231                 v = me.normalizeValue(thumb.value);
122232                 if (v !== thumb.value) {
122233                     // delete this.value;
122234                     me.setValue(i, v, false);
122235                 } else {
122236                     thumb.move(me.translateValue(v), false);
122237                 }
122238             }
122239         }
122240     },
122241
122242     /**
122243      * @private
122244      * Returns the ratio of pixels to mapped values. e.g. if the slider is 200px wide and maxValue - minValue is 100,
122245      * the ratio is 2
122246      * @return {Number} The ratio of pixels to mapped values
122247      */
122248     getRatio : function() {
122249         var w = this.innerEl.getWidth(),
122250             v = this.maxValue - this.minValue;
122251         return v === 0 ? w : (w/v);
122252     },
122253
122254     /**
122255      * @private
122256      * Returns a snapped, constrained value when given a desired value
122257      * @param {Number} value Raw number value
122258      * @return {Number} The raw value rounded to the correct d.p. and constrained within the set max and min values
122259      */
122260     normalizeValue : function(v) {
122261         var me = this;
122262         
122263         v = me.doSnap(v);
122264         v = Ext.util.Format.round(v, me.decimalPrecision);
122265         v = Ext.Number.constrain(v, me.minValue, me.maxValue);
122266         return v;
122267     },
122268
122269     /**
122270      * Sets the minimum value for the slider instance. If the current value is less than the
122271      * minimum value, the current value will be changed.
122272      * @param {Number} val The new minimum value
122273      */
122274     setMinValue : function(val) {
122275         var me = this,
122276             i = 0,
122277             thumbs = me.thumbs,
122278             len = thumbs.length,
122279             t;
122280             
122281         me.minValue = val;
122282         me.inputEl.dom.setAttribute('aria-valuemin', val);
122283
122284         for (; i < len; ++i) {
122285             t = thumbs[i];
122286             t.value = t.value < val ? val : t.value;
122287         }
122288         me.syncThumbs();
122289     },
122290
122291     /**
122292      * Sets the maximum value for the slider instance. If the current value is more than the
122293      * maximum value, the current value will be changed.
122294      * @param {Number} val The new maximum value
122295      */
122296     setMaxValue : function(val) {
122297         var me = this,
122298             i = 0,
122299             thumbs = me.thumbs,
122300             len = thumbs.length,
122301             t;
122302             
122303         me.maxValue = val;
122304         me.inputEl.dom.setAttribute('aria-valuemax', val);
122305
122306         for (; i < len; ++i) {
122307             t = thumbs[i];
122308             t.value = t.value > val ? val : t.value;
122309         }
122310         me.syncThumbs();
122311     },
122312
122313     /**
122314      * Programmatically sets the value of the Slider. Ensures that the value is constrained within
122315      * the minValue and maxValue.
122316      * @param {Number} index Index of the thumb to move
122317      * @param {Number} value The value to set the slider to. (This will be constrained within minValue and maxValue)
122318      * @param {Boolean} animate Turn on or off animation, defaults to true
122319      */
122320     setValue : function(index, value, animate, changeComplete) {
122321         var me = this,
122322             thumb = me.thumbs[index];
122323
122324         // ensures value is contstrained and snapped
122325         value = me.normalizeValue(value);
122326
122327         if (value !== thumb.value && me.fireEvent('beforechange', me, value, thumb.value, thumb) !== false) {
122328             thumb.value = value;
122329             if (me.rendered) {
122330                 // TODO this only handles a single value; need a solution for exposing multiple values to aria.
122331                 // Perhaps this should go on each thumb element rather than the outer element.
122332                 me.inputEl.set({
122333                     'aria-valuenow': value,
122334                     'aria-valuetext': value
122335                 });
122336
122337                 thumb.move(me.translateValue(value), Ext.isDefined(animate) ? animate !== false : me.animate);
122338
122339                 me.fireEvent('change', me, value, thumb);
122340                 if (changeComplete) {
122341                     me.fireEvent('changecomplete', me, value, thumb);
122342                 }
122343             }
122344         }
122345     },
122346
122347     /**
122348      * @private
122349      */
122350     translateValue : function(v) {
122351         var ratio = this.getRatio();
122352         return (v * ratio) - (this.minValue * ratio) - this.halfThumb;
122353     },
122354
122355     /**
122356      * @private
122357      * Given a pixel location along the slider, returns the mapped slider value for that pixel.
122358      * E.g. if we have a slider 200px wide with minValue = 100 and maxValue = 500, reverseValue(50)
122359      * returns 200
122360      * @param {Number} pos The position along the slider to return a mapped value for
122361      * @return {Number} The mapped value for the given position
122362      */
122363     reverseValue : function(pos) {
122364         var ratio = this.getRatio();
122365         return (pos + (this.minValue * ratio)) / ratio;
122366     },
122367
122368     // private
122369     focus : function() {
122370         this.focusEl.focus(10);
122371     },
122372
122373     //private
122374     onDisable: function() {
122375         var me = this,
122376             i = 0,
122377             thumbs = me.thumbs,
122378             len = thumbs.length,
122379             thumb,
122380             el,
122381             xy;
122382             
122383         me.callParent();
122384
122385         for (; i < len; i++) {
122386             thumb = thumbs[i];
122387             el = thumb.el;
122388
122389             thumb.disable();
122390
122391             if(Ext.isIE) {
122392                 //IE breaks when using overflow visible and opacity other than 1.
122393                 //Create a place holder for the thumb and display it.
122394                 xy = el.getXY();
122395                 el.hide();
122396
122397                 me.innerEl.addCls(me.disabledCls).dom.disabled = true;
122398
122399                 if (!me.thumbHolder) {
122400                     me.thumbHolder = me.endEl.createChild({cls: Ext.baseCSSPrefix + 'slider-thumb ' + me.disabledCls});
122401                 }
122402
122403                 me.thumbHolder.show().setXY(xy);
122404             }
122405         }
122406     },
122407
122408     //private
122409     onEnable: function() {
122410         var me = this,
122411             i = 0,
122412             thumbs = me.thumbs,
122413             len = thumbs.length,
122414             thumb,
122415             el;
122416             
122417         this.callParent();
122418
122419         for (; i < len; i++) {
122420             thumb = thumbs[i];
122421             el = thumb.el;
122422
122423             thumb.enable();
122424
122425             if (Ext.isIE) {
122426                 me.innerEl.removeCls(me.disabledCls).dom.disabled = false;
122427
122428                 if (me.thumbHolder) {
122429                     me.thumbHolder.hide();
122430                 }
122431
122432                 el.show();
122433                 me.syncThumbs();
122434             }
122435         }
122436     },
122437
122438     /**
122439      * Synchronizes thumbs position to the proper proportion of the total component width based
122440      * on the current slider {@link #value}.  This will be called automatically when the Slider
122441      * is resized by a layout, but if it is rendered auto width, this method can be called from
122442      * another resize handler to sync the Slider if necessary.
122443      */
122444     syncThumbs : function() {
122445         if (this.rendered) {
122446             var thumbs = this.thumbs,
122447                 length = thumbs.length,
122448                 i = 0;
122449
122450             for (; i < length; i++) {
122451                 thumbs[i].move(this.translateValue(thumbs[i].value));
122452             }
122453         }
122454     },
122455
122456     /**
122457      * Returns the current value of the slider
122458      * @param {Number} index The index of the thumb to return a value for
122459      * @return {Number/Array} The current value of the slider at the given index, or an array of
122460      * all thumb values if no index is given.
122461      */
122462     getValue : function(index) {
122463         return Ext.isNumber(index) ? this.thumbs[index].value : this.getValues();
122464     },
122465
122466     /**
122467      * Returns an array of values - one for the location of each thumb
122468      * @return {Array} The set of thumb values
122469      */
122470     getValues: function() {
122471         var values = [],
122472             i = 0,
122473             thumbs = this.thumbs,
122474             len = thumbs.length;
122475
122476         for (; i < len; i++) {
122477             values.push(thumbs[i].value);
122478         }
122479
122480         return values;
122481     },
122482
122483     getSubmitValue: function() {
122484         var me = this;
122485         return (me.disabled || !me.submitValue) ? null : me.getValue();
122486     },
122487
122488     reset: function() {
122489         var me = this,
122490             Array = Ext.Array;
122491         Array.forEach(Array.from(me.originalValue), function(val, i) {
122492             me.setValue(i, val);
122493         });
122494         me.clearInvalid();
122495         // delete here so we reset back to the original state
122496         delete me.wasValid;
122497     },
122498
122499     // private
122500     beforeDestroy : function() {
122501         var me = this;
122502         
122503         Ext.destroyMembers(me.innerEl, me.endEl, me.focusEl);
122504         Ext.each(me.thumbs, function(thumb) {
122505             Ext.destroy(thumb);
122506         }, me);
122507
122508         me.callParent();
122509     },
122510
122511     statics: {
122512         // Method overrides to support slider with vertical orientation
122513         Vertical: {
122514             getRatio : function() {
122515                 var h = this.innerEl.getHeight(),
122516                     v = this.maxValue - this.minValue;
122517                 return h/v;
122518             },
122519
122520             onClickChange : function(local) {
122521                 var me = this,
122522                     thumb, index, bottom;
122523
122524                 if (local.left > me.clickRange[0] && local.left < me.clickRange[1]) {
122525                     thumb = me.getNearest(local, 'top');
122526                     if (!thumb.disabled) {
122527                         index = thumb.index;
122528                         bottom =  me.reverseValue(me.innerEl.getHeight() - local.top);
122529
122530                         me.setValue(index, Ext.util.Format.round(me.minValue + bottom, me.decimalPrecision), undefined, true);
122531                     }
122532                 }
122533             }
122534         }
122535     }
122536 });
122537
122538 /**
122539  * @class Ext.slider.Single
122540  * @extends Ext.slider.Multi
122541  * Slider which supports vertical or horizontal orientation, keyboard adjustments,
122542  * configurable snapping, axis clicking and animation. Can be added as an item to
122543  * any container. 
122544  * {@img Ext.slider.Single/Ext.slider.Single.png Ext.slider.Single component}
122545  * Example usage:
122546 <pre><code>
122547     Ext.create('Ext.slider.Single', {
122548         width: 200,
122549         value: 50,
122550         increment: 10,
122551         minValue: 0,
122552         maxValue: 100,
122553         renderTo: Ext.getBody()
122554     });
122555 </code></pre>
122556  * The class Ext.slider.Single is aliased to Ext.Slider for backwards compatibility.
122557  * @xtype slider
122558  */
122559 Ext.define('Ext.slider.Single', {
122560     extend: 'Ext.slider.Multi',
122561     alias: ['widget.slider', 'widget.sliderfield'],
122562     alternateClassName: ['Ext.Slider', 'Ext.form.SliderField', 'Ext.slider.SingleSlider', 'Ext.slider.Slider'],
122563
122564     /**
122565      * Returns the current value of the slider
122566      * @return {Number} The current value of the slider
122567      */
122568     getValue: function() {
122569         //just returns the value of the first thumb, which should be the only one in a single slider
122570         return this.callParent([0]);
122571     },
122572
122573     /**
122574      * Programmatically sets the value of the Slider. Ensures that the value is constrained within
122575      * the minValue and maxValue.
122576      * @param {Number} value The value to set the slider to. (This will be constrained within minValue and maxValue)
122577      * @param {Boolean} animate Turn on or off animation, defaults to true
122578      */
122579     setValue: function(value, animate) {
122580         var args = Ext.toArray(arguments),
122581             len  = args.length;
122582
122583         //this is to maintain backwards compatiblity for sliders with only one thunb. Usually you must pass the thumb
122584         //index to setValue, but if we only have one thumb we inject the index here first if given the multi-slider
122585         //signature without the required index. The index will always be 0 for a single slider
122586         if (len == 1 || (len <= 3 && typeof arguments[1] != 'number')) {
122587             args.unshift(0);
122588         }
122589
122590         return this.callParent(args);
122591     },
122592
122593     // private
122594     getNearest : function(){
122595         // Since there's only 1 thumb, it's always the nearest
122596         return this.thumbs[0];
122597     }
122598 });
122599
122600 /**
122601  * @author Ed Spencer
122602  * @class Ext.tab.Tab
122603  * @extends Ext.button.Button
122604  * 
122605  * <p>Represents a single Tab in a {@link Ext.tab.Panel TabPanel}. A Tab is simply a slightly customized {@link Ext.button.Button Button}, 
122606  * styled to look like a tab. Tabs are optionally closable, and can also be disabled. 99% of the time you will not
122607  * need to create Tabs manually as the framework does so automatically when you use a {@link Ext.tab.Panel TabPanel}</p>
122608  *
122609  * @xtype tab
122610  */
122611 Ext.define('Ext.tab.Tab', {
122612     extend: 'Ext.button.Button',
122613     alias: 'widget.tab',
122614     
122615     requires: [
122616         'Ext.layout.component.Tab',
122617         'Ext.util.KeyNav'
122618     ],
122619
122620     componentLayout: 'tab',
122621
122622     isTab: true,
122623
122624     baseCls: Ext.baseCSSPrefix + 'tab',
122625
122626     /**
122627      * @cfg {String} activeCls
122628      * The CSS class to be applied to a Tab when it is active. Defaults to 'x-tab-active'.
122629      * Providing your own CSS for this class enables you to customize the active state.
122630      */
122631     activeCls: 'active',
122632     
122633     /**
122634      * @cfg {String} disabledCls
122635      * The CSS class to be applied to a Tab when it is disabled. Defaults to 'x-tab-disabled'.
122636      */
122637
122638     /**
122639      * @cfg {String} closableCls
122640      * The CSS class which is added to the tab when it is closable
122641      */
122642     closableCls: 'closable',
122643
122644     /**
122645      * @cfg {Boolean} closable True to make the Tab start closable (the close icon will be visible). Defaults to true
122646      */
122647     closable: true,
122648
122649     /**
122650      * @cfg {String} closeText 
122651      * The accessible text label for the close button link; only used when {@link #closable} = true.
122652      * Defaults to 'Close Tab'.
122653      */
122654     closeText: 'Close Tab',
122655
122656     /**
122657      * @property Boolean
122658      * Read-only property indicating that this tab is currently active. This is NOT a public configuration.
122659      */
122660     active: false,
122661
122662     /**
122663      * @property closable
122664      * @type Boolean
122665      * True if the tab is currently closable
122666      */
122667
122668     scale: false,
122669
122670     position: 'top',
122671     
122672     initComponent: function() {
122673         var me = this;
122674
122675         me.addEvents(
122676             /**
122677              * @event activate
122678              * @param {Ext.tab.Tab} this
122679              */
122680             'activate',
122681
122682             /**
122683              * @event deactivate
122684              * @param {Ext.tab.Tab} this
122685              */
122686             'deactivate',
122687
122688             /**
122689              * @event beforeclose
122690              * Fires if the user clicks on the Tab's close button, but before the {@link #close} event is fired. Return
122691              * false from any listener to stop the close event being fired
122692              * @param {Ext.tab.Tab} tab The Tab object
122693              */
122694             'beforeclose',
122695
122696             /**
122697              * @event beforeclose
122698              * Fires to indicate that the tab is to be closed, usually because the user has clicked the close button.
122699              * @param {Ext.tab.Tab} tab The Tab object
122700              */
122701             'close'
122702         );
122703         
122704         me.callParent(arguments);
122705
122706         if (me.card) {
122707             me.setCard(me.card);
122708         }
122709     },
122710
122711     /**
122712      * @ignore
122713      */
122714     onRender: function() {
122715         var me = this;
122716         
122717         me.addClsWithUI(me.position);
122718         
122719         // Set all the state classNames, as they need to include the UI
122720         // me.disabledCls = me.getClsWithUIs('disabled');
122721
122722         me.syncClosableUI();
122723
122724         me.callParent(arguments);
122725         
122726         if (me.active) {
122727             me.activate(true);
122728         }
122729
122730         me.syncClosableElements();
122731         
122732         me.keyNav = Ext.create('Ext.util.KeyNav', me.el, {
122733             enter: me.onEnterKey,
122734             del: me.onDeleteKey,
122735             scope: me
122736         });
122737     },
122738     
122739     // inherit docs
122740     enable : function(silent) {
122741         var me = this;
122742
122743         me.callParent(arguments);
122744         
122745         me.removeClsWithUI(me.position + '-disabled');
122746
122747         return me;
122748     },
122749
122750     // inherit docs
122751     disable : function(silent) {
122752         var me = this;
122753         
122754         me.callParent(arguments);
122755         
122756         me.addClsWithUI(me.position + '-disabled');
122757
122758         return me;
122759     },
122760     
122761     /**
122762      * @ignore
122763      */
122764     onDestroy: function() {
122765         var me = this;
122766
122767         if (me.closeEl) {
122768             me.closeEl.un('click', Ext.EventManager.preventDefault);
122769             me.closeEl = null;
122770         }
122771
122772         Ext.destroy(me.keyNav);
122773         delete me.keyNav;
122774
122775         me.callParent(arguments);
122776     },
122777
122778     /**
122779      * Sets the tab as either closable or not
122780      * @param {Boolean} closable Pass false to make the tab not closable. Otherwise the tab will be made closable (eg a
122781      * close button will appear on the tab)
122782      */
122783     setClosable: function(closable) {
122784         var me = this;
122785
122786         // Closable must be true if no args
122787         closable = (!arguments.length || !!closable);
122788
122789         if (me.closable != closable) {
122790             me.closable = closable;
122791
122792             // set property on the user-facing item ('card'):
122793             if (me.card) {
122794                 me.card.closable = closable;
122795             }
122796
122797             me.syncClosableUI();
122798
122799             if (me.rendered) {
122800                 me.syncClosableElements();
122801
122802                 // Tab will change width to accommodate close icon
122803                 me.doComponentLayout();
122804                 if (me.ownerCt) {
122805                     me.ownerCt.doLayout();
122806                 }
122807             }
122808         }
122809     },
122810
122811     /**
122812      * This method ensures that the closeBtn element exists or not based on 'closable'.
122813      * @private
122814      */
122815     syncClosableElements: function () {
122816         var me = this;
122817
122818         if (me.closable) {
122819             if (!me.closeEl) {
122820                 me.closeEl = me.el.createChild({
122821                     tag: 'a',
122822                     cls: me.baseCls + '-close-btn',
122823                     href: '#',
122824                     html: me.closeText,
122825                     title: me.closeText
122826                 }).on('click', Ext.EventManager.preventDefault);  // mon ???
122827             }
122828         } else {
122829             var closeEl = me.closeEl;
122830             if (closeEl) {
122831                 closeEl.un('click', Ext.EventManager.preventDefault);
122832                 closeEl.remove();
122833                 me.closeEl = null;
122834             }
122835         }
122836     },
122837
122838     /**
122839      * This method ensures that the UI classes are added or removed based on 'closable'.
122840      * @private
122841      */
122842     syncClosableUI: function () {
122843         var me = this, classes = [me.closableCls, me.closableCls + '-' + me.position];
122844
122845         if (me.closable) {
122846             me.addClsWithUI(classes);
122847         } else {
122848             me.removeClsWithUI(classes);
122849         }
122850     },
122851
122852     /**
122853      * Sets this tab's attached card. Usually this is handled automatically by the {@link Ext.tab.Panel} that this Tab
122854      * belongs to and would not need to be done by the developer
122855      * @param {Ext.Component} card The card to set
122856      */
122857     setCard: function(card) {
122858         var me = this;
122859
122860         me.card = card;
122861         me.setText(me.title || card.title);
122862         me.setIconCls(me.iconCls || card.iconCls);
122863     },
122864
122865     /**
122866      * @private
122867      * Listener attached to click events on the Tab's close button
122868      */
122869     onCloseClick: function() {
122870         var me = this;
122871
122872         if (me.fireEvent('beforeclose', me) !== false) {
122873             if (me.tabBar) {
122874                 me.tabBar.closeTab(me);
122875             }
122876
122877             me.fireEvent('close', me);
122878         }
122879     },
122880     
122881     /**
122882      * @private
122883      */
122884     onEnterKey: function(e) {
122885         var me = this;
122886         
122887         if (me.tabBar) {
122888             me.tabBar.onClick(e, me.el);
122889         }
122890     },
122891     
122892    /**
122893      * @private
122894      */
122895     onDeleteKey: function(e) {
122896         var me = this;
122897         
122898         if (me.closable) {
122899             me.onCloseClick();
122900         }
122901     },
122902     
122903     // @private
122904     activate : function(supressEvent) {
122905         var me = this;
122906         
122907         me.active = true;
122908         me.addClsWithUI([me.activeCls, me.position + '-' + me.activeCls]);
122909
122910         if (supressEvent !== true) {
122911             me.fireEvent('activate', me);
122912         }
122913     },
122914
122915     // @private
122916     deactivate : function(supressEvent) {
122917         var me = this;
122918         
122919         me.active = false;
122920         me.removeClsWithUI([me.activeCls, me.position + '-' + me.activeCls]);
122921         
122922         if (supressEvent !== true) {
122923             me.fireEvent('deactivate', me);
122924         }
122925     }
122926 });
122927
122928 /**
122929  * @author Ed Spencer
122930  * @class Ext.tab.Bar
122931  * @extends Ext.panel.Header
122932  * <p>TabBar is used internally by a {@link Ext.tab.Panel TabPanel} and wouldn't usually need to be created manually.</p>
122933  *
122934  * @xtype tabbar
122935  */
122936 Ext.define('Ext.tab.Bar', {
122937     extend: 'Ext.panel.Header',
122938     alias: 'widget.tabbar',
122939     baseCls: Ext.baseCSSPrefix + 'tab-bar',
122940
122941     requires: [
122942         'Ext.tab.Tab',
122943         'Ext.FocusManager'
122944     ],
122945
122946     // @private
122947     defaultType: 'tab',
122948
122949     /**
122950      * @cfg Boolean plain
122951      * True to not show the full background on the tabbar
122952      */
122953     plain: false,
122954
122955     // @private
122956     renderTpl: [
122957         '<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>',
122958         '<div class="{baseCls}-strip<tpl if="ui"> {baseCls}-strip-{ui}<tpl for="uiCls"> {parent.baseCls}-strip-{parent.ui}-{.}</tpl></tpl>"></div>'
122959     ],
122960
122961     /**
122962      * @cfg {Number} minTabWidth The minimum width for each tab. Defaults to <tt>30</tt>.
122963      */
122964     minTabWidth: 30,
122965
122966     /**
122967      * @cfg {Number} maxTabWidth The maximum width for each tab. Defaults to <tt>undefined</tt>.
122968      */
122969     maxTabWidth: undefined,
122970
122971     // @private
122972     initComponent: function() {
122973         var me = this,
122974             keys;
122975
122976         if (me.plain) {
122977             me.setUI(me.ui + '-plain');
122978         }
122979         
122980         me.addClsWithUI(me.dock);
122981
122982         me.addEvents(
122983             /**
122984              * @event change
122985              * Fired when the currently-active tab has changed
122986              * @param {Ext.tab.Bar} tabBar The TabBar
122987              * @param {Ext.Tab} tab The new Tab
122988              * @param {Ext.Component} card The card that was just shown in the TabPanel
122989              */
122990             'change'
122991         );
122992
122993         Ext.applyIf(this.renderSelectors, {
122994             body : '.' + this.baseCls + '-body',
122995             strip: '.' + this.baseCls + '-strip'
122996         });
122997         me.callParent(arguments);
122998
122999         // TabBar must override the Header's align setting.
123000         me.layout.align = (me.orientation == 'vertical') ? 'left' : 'top';
123001         me.layout.overflowHandler = Ext.create('Ext.layout.container.boxOverflow.Scroller', me.layout);
123002         me.items.removeAt(me.items.getCount() - 1);
123003         me.items.removeAt(me.items.getCount() - 1);
123004         
123005         // Subscribe to Ext.FocusManager for key navigation
123006         keys = me.orientation == 'vertical' ? ['up', 'down'] : ['left', 'right'];
123007         Ext.FocusManager.subscribe(me, {
123008             keys: keys
123009         });
123010     },
123011
123012     // @private
123013     onAdd: function(tab) {
123014         var me = this,
123015             tabPanel = me.tabPanel,
123016             hasOwner = !!tabPanel;
123017
123018         me.callParent(arguments);
123019         tab.position = me.dock;
123020         if (hasOwner) {
123021             tab.minWidth = tabPanel.minTabWidth;
123022         }
123023         else {
123024             tab.minWidth = me.minTabWidth + (tab.iconCls ? 25 : 0);
123025         }
123026         tab.maxWidth = me.maxTabWidth || (hasOwner ? tabPanel.maxTabWidth : undefined);
123027     },
123028
123029     // @private
123030     afterRender: function() {
123031         var me = this;
123032
123033         me.mon(me.el, {
123034             scope: me,
123035             click: me.onClick,
123036             delegate: '.' + Ext.baseCSSPrefix + 'tab'
123037         });
123038         me.callParent(arguments);
123039         
123040     },
123041
123042     afterComponentLayout : function() {
123043         var me = this;
123044         
123045         me.callParent(arguments);
123046         me.strip.setWidth(me.el.getWidth());
123047     },
123048
123049     // @private
123050     onClick: function(e, target) {
123051         // The target might not be a valid tab el.
123052         var tab = Ext.getCmp(target.id),
123053             tabPanel = this.tabPanel;
123054
123055         target = e.getTarget();
123056
123057         if (tab && tab.isDisabled && !tab.isDisabled()) {
123058             if (tab.closable && target === tab.closeEl.dom) {
123059                 tab.onCloseClick();
123060             } else {
123061                 this.setActiveTab(tab);
123062                 if (tabPanel) {
123063                     tabPanel.setActiveTab(tab.card);
123064                 }
123065                 tab.focus();
123066             }
123067         }
123068     },
123069
123070     /**
123071      * @private
123072      * Closes the given tab by removing it from the TabBar and removing the corresponding card from the TabPanel
123073      * @param {Ext.Tab} tab The tab to close
123074      */
123075     closeTab: function(tab) {
123076         var card    = tab.card,
123077             tabPanel = this.tabPanel,
123078             nextTab;
123079
123080         if (tab.active && this.items.getCount() > 1) {
123081             nextTab = tab.next('tab') || this.items.items[0];
123082             this.setActiveTab(nextTab);
123083             if (tabPanel) {
123084                 tabPanel.setActiveTab(nextTab.card);
123085             }
123086         }
123087         this.remove(tab);
123088
123089         if (tabPanel && card) {
123090             tabPanel.remove(card);
123091         }
123092         
123093         if (nextTab) {
123094             nextTab.focus();
123095         }
123096     },
123097
123098     /**
123099      * @private
123100      * Marks the given tab as active
123101      * @param {Ext.Tab} tab The tab to mark active
123102      */
123103     setActiveTab: function(tab) {
123104         if (tab.disabled) {
123105             return;
123106         }
123107         var me = this;
123108         if (me.activeTab) {
123109             me.activeTab.deactivate();
123110         }
123111         tab.activate();
123112         
123113         if (me.rendered) {
123114             me.layout.layout();
123115             tab.el.scrollIntoView(me.layout.getRenderTarget());
123116         }
123117         me.activeTab = tab;
123118         me.fireEvent('change', me, tab, tab.card);
123119     }
123120 });
123121 /**
123122  * @author Ed Spencer, Tommy Maintz, Brian Moeskau
123123  * @class Ext.tab.Panel
123124  * @extends Ext.panel.Panel
123125
123126 A basic tab container. TabPanels can be used exactly like a standard {@link Ext.panel.Panel} for layout purposes, but also 
123127 have special support for containing child Components (`{@link Ext.container.Container#items items}`) that are managed 
123128 using a {@link Ext.layout.container.Card CardLayout layout manager}, and displayed as separate tabs.
123129
123130 __Note:__
123131
123132 By default, a tab's close tool _destroys_ the child tab Component and all its descendants. This makes the child tab 
123133 Component, and all its descendants __unusable__. To enable re-use of a tab, configure the TabPanel with `{@link #autoDestroy autoDestroy: false}`.
123134
123135 __TabPanel's layout:__
123136
123137 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 
123138 header hidden by default because the Tab will automatically take the Panel's configured title and icon.
123139
123140 TabPanels use their {@link Ext.panel.Panel#header header} or {@link Ext.panel.Panel#footer footer} element (depending on the {@link #tabPosition} 
123141 configuration) to accommodate the tab selector buttons. This means that a TabPanel will not display any configured title, and will not display any 
123142 configured header {@link Ext.panel.Panel#tools tools}.
123143
123144 To display a header, embed the TabPanel in a {@link Ext.panel.Panel Panel} which uses `{@link Ext.container.Container#layout layout:'fit'}`.
123145
123146 __Examples:__
123147
123148 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. 
123149 If you do not set an {@link #activeTab}, no tabs will be active by default.
123150 {@img Ext.tab.Panel/Ext.tab.Panel1.png TabPanel component}
123151 Example usage:
123152
123153     Ext.create('Ext.tab.Panel', {
123154         width: 300,
123155         height: 200,
123156         activeTab: 0,
123157         items: [
123158             {
123159                 title: 'Tab 1',
123160                 bodyPadding: 10,
123161                 html : 'A simple tab'
123162             },
123163             {
123164                 title: 'Tab 2',
123165                 html : 'Another one'
123166             }
123167         ],
123168         renderTo : Ext.getBody()
123169     }); 
123170     
123171 It is easy to control the visibility of items in the tab bar. Specify hidden: true to have the
123172 tab button hidden initially. Items can be subsequently hidden and show by accessing the
123173 tab property on the child item.
123174
123175 Example usage:
123176     
123177     var tabs = Ext.create('Ext.tab.Panel', {
123178         width: 400,
123179         height: 400,
123180         renderTo: document.body,
123181         items: [{
123182             title: 'Home',
123183             html: 'Home',
123184             itemId: 'home'
123185         }, {
123186             title: 'Users',
123187             html: 'Users',
123188             itemId: 'users',
123189             hidden: true
123190         }, {
123191             title: 'Tickets',
123192             html: 'Tickets',
123193             itemId: 'tickets'
123194         }]    
123195     });
123196     
123197     setTimeout(function(){
123198         tabs.child('#home').tab.hide();
123199         var users = tabs.child('#users');
123200         users.tab.show();
123201         tabs.setActiveTab(users);
123202     }, 1000);
123203
123204 You can remove the background of the TabBar by setting the {@link #plain} property to `false`.
123205
123206 Example usage:
123207
123208     Ext.create('Ext.tab.Panel', {
123209         width: 300,
123210         height: 200,
123211         activeTab: 0,
123212         plain: true,
123213         items: [
123214             {
123215                 title: 'Tab 1',
123216                 bodyPadding: 10,
123217                 html : 'A simple tab'
123218             },
123219             {
123220                 title: 'Tab 2',
123221                 html : 'Another one'
123222             }
123223         ],
123224         renderTo : Ext.getBody()
123225     }); 
123226
123227 Another useful configuration of TabPanel is {@link #tabPosition}. This allows you to change the position where the tabs are displayed. The available 
123228 options for this are `'top'` (default) and `'bottom'`.
123229 {@img Ext.tab.Panel/Ext.tab.Panel2.png TabPanel component}
123230 Example usage:
123231
123232     Ext.create('Ext.tab.Panel', {
123233         width: 300,
123234         height: 200,
123235         activeTab: 0,
123236         bodyPadding: 10,        
123237         tabPosition: 'bottom',
123238         items: [
123239             {
123240                 title: 'Tab 1',
123241                 html : 'A simple tab'
123242             },
123243             {
123244                 title: 'Tab 2',
123245                 html : 'Another one'
123246             }
123247         ],
123248         renderTo : Ext.getBody()
123249     }); 
123250
123251 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 
123252 an instance of a tab.
123253
123254 Example usage:
123255
123256     var tabs = Ext.create('Ext.tab.Panel', {
123257         items: [
123258             {
123259                 id   : 'my-tab',
123260                 title: 'Tab 1',
123261                 html : 'A simple tab'
123262             },
123263             {
123264                 title: 'Tab 2',
123265                 html : 'Another one'
123266             }
123267         ],
123268         renderTo : Ext.getBody()
123269     });
123270     
123271     var tab = Ext.getCmp('my-tab');
123272     
123273     Ext.create('Ext.button.Button', {
123274         renderTo: Ext.getBody(),
123275         text    : 'Select the first tab',
123276         scope   : this,
123277         handler : function() {
123278             tabs.setActiveTab(tab);
123279         }
123280     });
123281     
123282     Ext.create('Ext.button.Button', {
123283         text    : 'Select the second tab',
123284         scope   : this,
123285         handler : function() {
123286             tabs.setActiveTab(1);
123287         },
123288         renderTo : Ext.getBody()        
123289     });
123290
123291 The {@link #getActiveTab} is a another useful method in TabPanel which will return the current active tab.
123292
123293 Example usage:
123294
123295     var tabs = Ext.create('Ext.tab.Panel', {
123296         items: [
123297             {
123298                 title: 'Tab 1',
123299                 html : 'A simple tab'
123300             },
123301             {
123302                 title: 'Tab 2',
123303                 html : 'Another one'
123304             }
123305         ],
123306         renderTo : Ext.getBody()        
123307     });
123308     
123309     Ext.create('Ext.button.Button', {
123310         text    : 'Get active tab',
123311         scope   : this,
123312         handler : function() {
123313             var tab = tabs.getActiveTab();
123314             alert('Current tab: ' + tab.title);
123315         },
123316         renderTo : Ext.getBody()        
123317     });
123318
123319 Adding a new tab is very simple with a TabPanel. You simple call the {@link #add} method with an config object for a panel.
123320
123321 Example usage:
123322
123323     var tabs = Ext.Create('Ext.tab.Panel', {
123324         items: [
123325             {
123326                 title: 'Tab 1',
123327                 html : 'A simple tab'
123328             },
123329             {
123330                 title: 'Tab 2',
123331                 html : 'Another one'
123332             }
123333         ],
123334         renderTo : Ext.getBody()        
123335     });
123336     
123337     Ext.create('Ext.button.Button', {
123338         text    : 'New tab',
123339         scope   : this,
123340         handler : function() {
123341             var tab = tabs.add({
123342                 title: 'Tab ' + (tabs.items.length + 1), //we use the tabs.items property to get the length of current items/tabs
123343                 html : 'Another one'
123344             });
123345             
123346             tabs.setActiveTab(tab);
123347         },
123348         renderTo : Ext.getBody()
123349     });
123350
123351 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.
123352
123353 Example usage:
123354
123355     var tabs = Ext.Create('Ext.tab.Panel', {        
123356         items: [
123357             {
123358                 title: 'Tab 1',
123359                 html : 'A simple tab'
123360             },
123361             {
123362                 id   : 'remove-this-tab',
123363                 title: 'Tab 2',
123364                 html : 'Another one'
123365             }
123366         ],
123367         renderTo : Ext.getBody()
123368     });
123369     
123370     Ext.Create('Ext.button.Button', {
123371         text    : 'Remove tab',
123372         scope   : this,
123373         handler : function() {
123374             var tab = Ext.getCmp('remove-this-tab');
123375             tabs.remove(tab);
123376         },
123377         renderTo : Ext.getBody()
123378     });
123379
123380  * @extends Ext.Panel
123381  * @constructor
123382  * @param {Object} config The configuration options
123383  * @xtype tabpanel
123384  * @markdown
123385  */
123386 Ext.define('Ext.tab.Panel', {
123387     extend: 'Ext.panel.Panel',
123388     alias: 'widget.tabpanel',
123389     alternateClassName: ['Ext.TabPanel'],
123390
123391     requires: ['Ext.layout.container.Card', 'Ext.tab.Bar'],
123392
123393     /**
123394      * @cfg {String} tabPosition The position where the tab strip should be rendered (defaults to <code>'top'</code>).
123395      * In 4.0, The only other supported value is <code>'bottom'</code>.
123396      */
123397     tabPosition : 'top',
123398     
123399     /**
123400      * @cfg {Object} tabBar Optional configuration object for the internal {@link Ext.tab.Bar}. If present, this is 
123401      * passed straight through to the TabBar's constructor
123402      */
123403
123404     /**
123405      * @cfg {Object} layout Optional configuration object for the internal {@link Ext.layout.container.Card card layout}.
123406      * If present, this is passed straight through to the layout's constructor
123407      */
123408
123409     /**
123410      * @cfg {Boolean} removePanelHeader True to instruct each Panel added to the TabContainer to not render its header 
123411      * element. This is to ensure that the title of the panel does not appear twice. Defaults to true.
123412      */
123413     removePanelHeader: true,
123414
123415     /**
123416      * @cfg Boolean plain
123417      * True to not show the full background on the TabBar
123418      */
123419     plain: false,
123420
123421     /**
123422      * @cfg {String} itemCls The class added to each child item of this TabPanel. Defaults to 'x-tabpanel-child'.
123423      */
123424     itemCls: 'x-tabpanel-child',
123425
123426     /**
123427      * @cfg {Number} minTabWidth The minimum width for a tab in the {@link #tabBar}. Defaults to <code>30</code>.
123428      */
123429
123430     /**
123431      * @cfg {Boolean} deferredRender
123432      * <p><tt>true</tt> by default to defer the rendering of child <tt>{@link Ext.container.Container#items items}</tt>
123433      * to the browsers DOM until a tab is activated. <tt>false</tt> will render all contained
123434      * <tt>{@link Ext.container.Container#items items}</tt> as soon as the {@link Ext.layout.container.Card layout}
123435      * is rendered. If there is a significant amount of content or a lot of heavy controls being
123436      * rendered into panels that are not displayed by default, setting this to <tt>true</tt> might
123437      * improve performance.</p>
123438      * <br><p>The <tt>deferredRender</tt> property is internally passed to the layout manager for
123439      * TabPanels ({@link Ext.layout.container.Card}) as its {@link Ext.layout.container.Card#deferredRender}
123440      * configuration value.</p>
123441      * <br><p><b>Note</b>: leaving <tt>deferredRender</tt> as <tt>true</tt> means that the content
123442      * within an unactivated tab will not be available</p>
123443      */
123444     deferredRender : true,
123445
123446     //inherit docs
123447     initComponent: function() {
123448         var me = this,
123449             dockedItems = me.dockedItems || [],
123450             activeTab = me.activeTab || 0;
123451
123452         me.layout = Ext.create('Ext.layout.container.Card', Ext.apply({
123453             owner: me,
123454             deferredRender: me.deferredRender,
123455             itemCls: me.itemCls
123456         }, me.layout));
123457
123458         /**
123459          * @property tabBar
123460          * @type Ext.TabBar
123461          * Internal reference to the docked TabBar
123462          */
123463         me.tabBar = Ext.create('Ext.tab.Bar', Ext.apply({}, me.tabBar, {
123464             dock: me.tabPosition,
123465             plain: me.plain,
123466             border: me.border,
123467             cardLayout: me.layout,
123468             tabPanel: me
123469         }));
123470
123471         if (dockedItems && !Ext.isArray(dockedItems)) {
123472             dockedItems = [dockedItems];
123473         }
123474
123475         dockedItems.push(me.tabBar);
123476         me.dockedItems = dockedItems;
123477
123478         me.addEvents(
123479             /**
123480              * @event beforetabchange
123481              * Fires before a tab change (activated by {@link #setActiveTab}). Return false in any listener to cancel
123482              * the tabchange
123483              * @param {Ext.tab.Panel} tabPanel The TabPanel
123484              * @param {Ext.Component} newCard The card that is about to be activated
123485              * @param {Ext.Component} oldCard The card that is currently active
123486              */
123487             'beforetabchange',
123488
123489             /**
123490              * @event tabchange
123491              * Fires when a new tab has been activated (activated by {@link #setActiveTab}).
123492              * @param {Ext.tab.Panel} tabPanel The TabPanel
123493              * @param {Ext.Component} newCard The newly activated item
123494              * @param {Ext.Component} oldCard The previously active item
123495              */
123496             'tabchange'
123497         );
123498         me.callParent(arguments);
123499
123500         //set the active tab
123501         me.setActiveTab(activeTab);
123502         //set the active tab after initial layout
123503         me.on('afterlayout', me.afterInitialLayout, me, {single: true});
123504     },
123505
123506     /**
123507      * @private
123508      * We have to wait until after the initial layout to visually activate the activeTab (if set).
123509      * The active tab has different margins than normal tabs, so if the initial layout happens with
123510      * a tab active, its layout will be offset improperly due to the active margin style. Waiting
123511      * until after the initial layout avoids this issue.
123512      */
123513     afterInitialLayout: function() {
123514         var me = this,
123515             card = me.getComponent(me.activeTab);
123516             
123517         if (card) {
123518             me.layout.setActiveItem(card);
123519         }
123520     },
123521
123522     /**
123523      * Makes the given card active (makes it the visible card in the TabPanel's CardLayout and highlights the Tab)
123524      * @param {Ext.Component} card The card to make active
123525      */
123526     setActiveTab: function(card) {
123527         var me = this,
123528             previous;
123529
123530         card = me.getComponent(card);
123531         if (card) {
123532             previous = me.getActiveTab();
123533             
123534             if (previous && previous !== card && me.fireEvent('beforetabchange', me, card, previous) === false) {
123535                 return false;
123536             }
123537             
123538             me.tabBar.setActiveTab(card.tab);
123539             me.activeTab = card;
123540             if (me.rendered) {
123541                 me.layout.setActiveItem(card);
123542             }
123543             
123544             if (previous && previous !== card) {
123545                 me.fireEvent('tabchange', me, card, previous);
123546             }
123547         }
123548     },
123549
123550     /**
123551      * Returns the item that is currently active inside this TabPanel. Note that before the TabPanel first activates a
123552      * child component this will return whatever was configured in the {@link #activeTab} config option 
123553      * @return {Ext.Component/Integer} The currently active item
123554      */
123555     getActiveTab: function() {
123556         return this.activeTab;
123557     },
123558
123559     /**
123560      * Returns the {@link Ext.tab.Bar} currently used in this TabPanel
123561      * @return {Ext.TabBar} The TabBar
123562      */
123563     getTabBar: function() {
123564         return this.tabBar;
123565     },
123566
123567     /**
123568      * @ignore
123569      * Makes sure we have a Tab for each item added to the TabPanel
123570      */
123571     onAdd: function(item, index) {
123572         var me = this;
123573
123574         item.tab = me.tabBar.insert(index, {
123575             xtype: 'tab',
123576             card: item,
123577             disabled: item.disabled,
123578             closable: item.closable,
123579             hidden: item.hidden,
123580             tabBar: me.tabBar
123581         });
123582         
123583         item.on({
123584             scope : me,
123585             enable: me.onItemEnable,
123586             disable: me.onItemDisable,
123587             beforeshow: me.onItemBeforeShow,
123588             iconchange: me.onItemIconChange,
123589             titlechange: me.onItemTitleChange
123590         });
123591
123592         if (item.isPanel) {
123593             if (me.removePanelHeader) {
123594                 item.preventHeader = true;
123595                 if (item.rendered) {
123596                     item.updateHeader();
123597                 }
123598             }
123599             if (item.isPanel && me.border) {
123600                 item.setBorder(false);
123601             }
123602         }
123603
123604         // ensure that there is at least one active tab
123605         if (this.rendered && me.items.getCount() === 1) {
123606             me.setActiveTab(0);
123607         }
123608     },
123609     
123610     /**
123611      * @private
123612      * Enable corresponding tab when item is enabled.
123613      */
123614     onItemEnable: function(item){
123615         item.tab.enable();
123616     },
123617
123618     /**
123619      * @private
123620      * Disable corresponding tab when item is enabled.
123621      */    
123622     onItemDisable: function(item){
123623         item.tab.disable();
123624     },
123625     
123626     /**
123627      * @private
123628      * Sets activeTab before item is shown.
123629      */
123630     onItemBeforeShow: function(item) {
123631         if (item !== this.activeTab) {
123632             this.setActiveTab(item);
123633             return false;
123634         }    
123635     },
123636     
123637     /**
123638      * @private
123639      * Update the tab iconCls when panel iconCls has been set or changed.
123640      */
123641     onItemIconChange: function(item, newIconCls) {
123642         item.tab.setIconCls(newIconCls);
123643         this.getTabBar().doLayout();
123644     },
123645     
123646     /**
123647      * @private
123648      * Update the tab title when panel title has been set or changed.
123649      */
123650     onItemTitleChange: function(item, newTitle) {
123651         item.tab.setText(newTitle);
123652         this.getTabBar().doLayout();
123653     },
123654
123655
123656     /**
123657      * @ignore
123658      * If we're removing the currently active tab, activate the nearest one. The item is removed when we call super,
123659      * so we can do preprocessing before then to find the card's index
123660      */
123661     doRemove: function(item, autoDestroy) {
123662         var me = this,
123663             items = me.items,
123664             /**
123665              * At this point the item hasn't been removed from the items collection.
123666              * As such, if we want to check if there are no more tabs left, we have to
123667              * check for one, as opposed to 0.
123668              */
123669             hasItemsLeft = items.getCount() > 1;
123670
123671         if (me.destroying || !hasItemsLeft) {
123672             me.activeTab = null;
123673         } else if (item === me.activeTab) {
123674              me.setActiveTab(item.next() || items.getAt(0)); 
123675         }
123676         me.callParent(arguments);
123677
123678         // Remove the two references
123679         delete item.tab.card;
123680         delete item.tab;
123681     },
123682
123683     /**
123684      * @ignore
123685      * Makes sure we remove the corresponding Tab when an item is removed
123686      */
123687     onRemove: function(item, autoDestroy) {
123688         var me = this;
123689         
123690         item.un({
123691             scope : me,
123692             enable: me.onItemEnable,
123693             disable: me.onItemDisable,
123694             beforeshow: me.onItemBeforeShow
123695         });
123696         if (!me.destroying && item.tab.ownerCt == me.tabBar) {
123697             me.tabBar.remove(item.tab);
123698         }
123699     }
123700 });
123701
123702 /**
123703  * @class Ext.toolbar.Spacer
123704  * @extends Ext.toolbar.Item
123705  * A simple element that adds extra horizontal space between items in a toolbar.
123706  * By default a 2px wide space is added via css specification:
123707  * <pre><code>
123708     .x-toolbar .x-toolbar-spacer {
123709         width:2px;
123710     }
123711  * </code></pre>
123712  * <p>Example usage:</p>
123713  * {@img Ext.toolbar.Spacer/Ext.toolbar.Spacer.png Toolbar Spacer}
123714  * <pre><code>
123715     Ext.create('Ext.panel.Panel', {
123716         title: 'Toolbar Spacer Example',
123717         width: 300,
123718         height: 200,
123719         tbar : [
123720             'Item 1',
123721             {xtype: 'tbspacer'}, // or ' '
123722             'Item 2',
123723             // space width is also configurable via javascript
123724             {xtype: 'tbspacer', width: 50}, // add a 50px space
123725             'Item 3'
123726         ],
123727         renderTo: Ext.getBody()
123728     });   
123729 </code></pre>
123730  * @constructor
123731  * Creates a new Spacer
123732  * @xtype tbspacer
123733  */
123734 Ext.define('Ext.toolbar.Spacer', {
123735     extend: 'Ext.Component',
123736     alias: 'widget.tbspacer',
123737     alternateClassName: 'Ext.Toolbar.Spacer',
123738     baseCls: Ext.baseCSSPrefix + 'toolbar-spacer',
123739     focusable: false
123740 });
123741 /**
123742  * @class Ext.tree.Column
123743  * @extends Ext.grid.column.Column
123744  * 
123745  * Provides indentation and folder structure markup for a Tree taking into account
123746  * depth and position within the tree hierarchy.
123747  * 
123748  * @private
123749  */
123750 Ext.define('Ext.tree.Column', {
123751     extend: 'Ext.grid.column.Column',
123752     alias: 'widget.treecolumn',
123753
123754     initComponent: function() {
123755         var origRenderer = this.renderer || this.defaultRenderer,
123756             origScope    = this.scope || window;
123757
123758         this.renderer = function(value, metaData, record, rowIdx, colIdx, store, view) {
123759             var buf   = [],
123760                 format = Ext.String.format,
123761                 depth = record.getDepth(),
123762                 treePrefix  = Ext.baseCSSPrefix + 'tree-',
123763                 elbowPrefix = treePrefix + 'elbow-',
123764                 expanderCls = treePrefix + 'expander',
123765                 imgText     = '<img src="{1}" class="{0}" />',
123766                 checkboxText= '<input type="button" role="checkbox" class="{0}" {1} />',
123767                 formattedValue = origRenderer.apply(origScope, arguments),
123768                 href = record.get('href'),
123769                 target = record.get('hrefTarget');
123770
123771             while (record) {
123772                 if (!record.isRoot() || (record.isRoot() && view.rootVisible)) {
123773                     if (record.getDepth() === depth) {
123774                         buf.unshift(format(imgText,
123775                             treePrefix + 'icon ' + 
123776                             treePrefix + 'icon' + (record.get('icon') ? '-inline ' : (record.isLeaf() ? '-leaf ' : '-parent ')) +
123777                             (record.get('iconCls') || ''),
123778                             record.get('icon') || Ext.BLANK_IMAGE_URL
123779                         ));
123780                         if (record.get('checked') !== null) {
123781                             buf.unshift(format(
123782                                 checkboxText,
123783                                 (treePrefix + 'checkbox') + (record.get('checked') ? ' ' + treePrefix + 'checkbox-checked' : ''),
123784                                 record.get('checked') ? 'aria-checked="true"' : ''
123785                             ));
123786                             if (record.get('checked')) {
123787                                 metaData.tdCls += (' ' + Ext.baseCSSPrefix + 'tree-checked');
123788                             }
123789                         }
123790                         if (record.isLast()) {
123791                             if (record.isLeaf() || (record.isLoaded() && !record.hasChildNodes())) {
123792                                 buf.unshift(format(imgText, (elbowPrefix + 'end'), Ext.BLANK_IMAGE_URL));
123793                             } else {
123794                                 buf.unshift(format(imgText, (elbowPrefix + 'end-plus ' + expanderCls), Ext.BLANK_IMAGE_URL));
123795                             }
123796                             
123797                         } else {
123798                             if (record.isLeaf() || (record.isLoaded() && !record.hasChildNodes())) {
123799                                 buf.unshift(format(imgText, (treePrefix + 'elbow'), Ext.BLANK_IMAGE_URL));
123800                             } else {
123801                                 buf.unshift(format(imgText, (elbowPrefix + 'plus ' + expanderCls), Ext.BLANK_IMAGE_URL));
123802                             }
123803                         }
123804                     } else {
123805                         if (record.isLast() || record.getDepth() === 0) {
123806                             buf.unshift(format(imgText, (elbowPrefix + 'empty'), Ext.BLANK_IMAGE_URL));
123807                         } else if (record.getDepth() !== 0) {
123808                             buf.unshift(format(imgText, (elbowPrefix + 'line'), Ext.BLANK_IMAGE_URL));
123809                         }                      
123810                     }
123811                 }
123812                 record = record.parentNode;
123813             }
123814             if (href) {
123815                 formattedValue = format('<a href="{0}" target="{1}">{2}</a>', href, target, formattedValue);
123816             }
123817             return buf.join("") + formattedValue;
123818         };
123819         this.callParent(arguments);
123820     },
123821
123822     defaultRenderer: function(value) {
123823         return value;
123824     }
123825 });
123826 /**
123827  * @class Ext.tree.View
123828  * @extends Ext.view.Table
123829  */
123830 Ext.define('Ext.tree.View', {
123831     extend: 'Ext.view.Table',
123832     alias: 'widget.treeview',
123833
123834     loadingCls: Ext.baseCSSPrefix + 'grid-tree-loading',
123835     expandedCls: Ext.baseCSSPrefix + 'grid-tree-node-expanded',
123836
123837     expanderSelector: '.' + Ext.baseCSSPrefix + 'tree-expander',
123838     checkboxSelector: '.' + Ext.baseCSSPrefix + 'tree-checkbox',
123839     expanderIconOverCls: Ext.baseCSSPrefix + 'tree-expander-over',
123840
123841     blockRefresh: true,
123842
123843     /** 
123844      * @cfg {Boolean} rootVisible <tt>false</tt> to hide the root node (defaults to <tt>true</tt>)
123845      */
123846     rootVisible: true,
123847
123848     /** 
123849      * @cfg {Boolean} animate <tt>true</tt> to enable animated expand/collapse (defaults to the value of {@link Ext#enableFx Ext.enableFx})
123850      */
123851
123852     expandDuration: 250,
123853     collapseDuration: 250,
123854     
123855     toggleOnDblClick: true,
123856
123857     initComponent: function() {
123858         var me = this;
123859         
123860         if (me.initialConfig.animate === undefined) {
123861             me.animate = Ext.enableFx;
123862         }
123863         
123864         me.store = Ext.create('Ext.data.NodeStore', {
123865             recursive: true,
123866             rootVisible: me.rootVisible,
123867             listeners: {
123868                 beforeexpand: me.onBeforeExpand,
123869                 expand: me.onExpand,
123870                 beforecollapse: me.onBeforeCollapse,
123871                 collapse: me.onCollapse,
123872                 scope: me
123873             }
123874         });
123875         
123876         if (me.node) {
123877             me.setRootNode(me.node);
123878         }
123879         me.animQueue = {};
123880         me.callParent(arguments);
123881     },
123882     
123883     onClear: function(){
123884         this.store.removeAll();    
123885     },
123886
123887     setRootNode: function(node) {
123888         var me = this;        
123889         me.store.setNode(node);
123890         me.node = node;
123891         if (!me.rootVisible) {
123892             node.expand();
123893         }
123894     },
123895     
123896     onRender: function() {
123897         var me = this,
123898             opts = {delegate: me.expanderSelector},
123899             el;
123900
123901         me.callParent(arguments);
123902
123903         el = me.el;
123904         el.on({
123905             scope: me,
123906             delegate: me.expanderSelector,
123907             mouseover: me.onExpanderMouseOver,
123908             mouseout: me.onExpanderMouseOut
123909         });
123910         el.on({
123911             scope: me,
123912             delegate: me.checkboxSelector,
123913             click: me.onCheckboxChange
123914         });
123915     },
123916
123917     onCheckboxChange: function(e, t) {
123918         var item = e.getTarget(this.getItemSelector(), this.getTargetEl()),
123919             record, value;
123920             
123921         if (item) {
123922             record = this.getRecord(item);
123923             value = !record.get('checked');
123924             record.set('checked', value);
123925             this.fireEvent('checkchange', record, value);
123926         }
123927     },
123928
123929     getChecked: function() {
123930         var checked = [];
123931         this.node.cascadeBy(function(rec){
123932             if (rec.get('checked')) {
123933                 checked.push(rec);
123934             }
123935         });
123936         return checked;
123937     },
123938     
123939     isItemChecked: function(rec){
123940         return rec.get('checked');
123941     },
123942
123943     createAnimWrap: function(record, index) {
123944         var thHtml = '',
123945             headerCt = this.panel.headerCt,
123946             headers = headerCt.getGridColumns(),
123947             i = 0, len = headers.length, item,
123948             node = this.getNode(record),
123949             tmpEl, nodeEl;
123950
123951         for (; i < len; i++) {
123952             item = headers[i];
123953             thHtml += '<th style="width: ' + (item.hidden ? 0 : item.getDesiredWidth()) + 'px; height: 0px;"></th>';
123954         }
123955
123956         nodeEl = Ext.get(node);        
123957         tmpEl = nodeEl.insertSibling({
123958             tag: 'tr',
123959             html: [
123960                 '<td colspan="' + headerCt.getColumnCount() + '">',
123961                     '<div class="' + Ext.baseCSSPrefix + 'tree-animator-wrap' + '">',
123962                         '<table class="' + Ext.baseCSSPrefix + 'grid-table" style="width: ' + headerCt.getFullWidth() + 'px;"><tbody>',
123963                             thHtml,
123964                         '</tbody></table>',
123965                     '</div>',
123966                 '</td>'
123967             ].join('')
123968         }, 'after');
123969
123970         return {
123971             record: record,
123972             node: node,
123973             el: tmpEl,
123974             expanding: false,
123975             collapsing: false,
123976             animating: false,
123977             animateEl: tmpEl.down('div'),
123978             targetEl: tmpEl.down('tbody')
123979         };
123980     },
123981
123982     getAnimWrap: function(parent) {
123983         if (!this.animate) {
123984             return null;
123985         }
123986
123987         // We are checking to see which parent is having the animation wrap
123988         while (parent) {
123989             if (parent.animWrap) {
123990                 return parent.animWrap;
123991             }
123992             parent = parent.parentNode;
123993         }
123994         return null;
123995     },
123996
123997     doAdd: function(nodes, records, index) {
123998         // If we are adding records which have a parent that is currently expanding
123999         // lets add them to the animation wrap
124000         var me = this,
124001             record = records[0],
124002             parent = record.parentNode,
124003             a = me.all.elements,
124004             relativeIndex = 0,
124005             animWrap = me.getAnimWrap(parent),
124006             targetEl, children, len;
124007
124008         if (!animWrap || !animWrap.expanding) {
124009             me.resetScrollers();
124010             return me.callParent(arguments);
124011         }
124012
124013         // We need the parent that has the animWrap, not the nodes parent
124014         parent = animWrap.record;
124015         
124016         // If there is an anim wrap we do our special magic logic
124017         targetEl = animWrap.targetEl;
124018         children = targetEl.dom.childNodes;
124019         
124020         // We subtract 1 from the childrens length because we have a tr in there with the th'es
124021         len = children.length - 1;
124022         
124023         // The relative index is the index in the full flat collection minus the index of the wraps parent
124024         relativeIndex = index - me.indexOf(parent) - 1;
124025         
124026         // If we are adding records to the wrap that have a higher relative index then there are currently children
124027         // it means we have to append the nodes to the wrap
124028         if (!len || relativeIndex >= len) {
124029             targetEl.appendChild(nodes);
124030         }
124031         // If there are already more children then the relative index it means we are adding child nodes of
124032         // some expanded node in the anim wrap. In this case we have to insert the nodes in the right location
124033         else {
124034             // +1 because of the tr with th'es that is already there
124035             Ext.fly(children[relativeIndex + 1]).insertSibling(nodes, 'before', true);
124036         }
124037         
124038         // We also have to update the CompositeElementLite collection of the DataView
124039         if (index < a.length) {
124040             a.splice.apply(a, [index, 0].concat(nodes));
124041         }
124042         else {            
124043             a.push.apply(a, nodes);
124044         }
124045         
124046         // If we were in an animation we need to now change the animation
124047         // because the targetEl just got higher.
124048         if (animWrap.isAnimating) {
124049             me.onExpand(parent);
124050         }
124051     },
124052     
124053     doRemove: function(record, index) {
124054         // If we are adding records which have a parent that is currently expanding
124055         // lets add them to the animation wrap
124056         var me = this,
124057             parent = record.parentNode,
124058             all = me.all,
124059             animWrap = me.getAnimWrap(record),
124060             node = all.item(index).dom;
124061
124062         if (!animWrap || !animWrap.collapsing) {
124063             me.resetScrollers();
124064             return me.callParent(arguments);
124065         }
124066
124067         animWrap.targetEl.appendChild(node);
124068         all.removeElement(index);
124069     },
124070
124071     onBeforeExpand: function(parent, records, index) {
124072         var me = this,
124073             animWrap;
124074             
124075         if (!me.animate) {
124076             return;
124077         }
124078
124079         if (me.getNode(parent)) {
124080             animWrap = me.getAnimWrap(parent);
124081             if (!animWrap) {
124082                 animWrap = parent.animWrap = me.createAnimWrap(parent);
124083                 animWrap.animateEl.setHeight(0);
124084             }
124085             else if (animWrap.collapsing) {
124086                 // If we expand this node while it is still expanding then we
124087                 // have to remove the nodes from the animWrap.
124088                 animWrap.targetEl.select(me.itemSelector).remove();
124089             } 
124090             animWrap.expanding = true;
124091             animWrap.collapsing = false;
124092         }
124093     },
124094
124095     onExpand: function(parent) {
124096         var me = this,
124097             queue = me.animQueue,
124098             id = parent.getId(),
124099             animWrap,
124100             animateEl, 
124101             targetEl,
124102             queueItem;        
124103         
124104         if (me.singleExpand) {
124105             me.ensureSingleExpand(parent);
124106         }
124107         
124108         animWrap = me.getAnimWrap(parent);
124109
124110         if (!animWrap) {
124111             me.resetScrollers();
124112             return;
124113         }
124114         
124115         animateEl = animWrap.animateEl;
124116         targetEl = animWrap.targetEl;
124117
124118         animateEl.stopAnimation();
124119         // @TODO: we are setting it to 1 because quirks mode on IE seems to have issues with 0
124120         queue[id] = true;
124121         animateEl.slideIn('t', {
124122             duration: me.expandDuration,
124123             listeners: {
124124                 scope: me,
124125                 lastframe: function() {
124126                     // Move all the nodes out of the anim wrap to their proper location
124127                     animWrap.el.insertSibling(targetEl.query(me.itemSelector), 'before');
124128                     animWrap.el.remove();
124129                     me.resetScrollers();
124130                     delete animWrap.record.animWrap;
124131                     delete queue[id];
124132                 }
124133             }
124134         });
124135         
124136         animWrap.isAnimating = true;
124137     },
124138     
124139     resetScrollers: function(){
124140         var panel = this.panel;
124141         
124142         panel.determineScrollbars();
124143         panel.invalidateScroller();
124144     },
124145
124146     onBeforeCollapse: function(parent, records, index) {
124147         var me = this,
124148             animWrap;
124149             
124150         if (!me.animate) {
124151             return;
124152         }
124153
124154         if (me.getNode(parent)) {
124155             animWrap = me.getAnimWrap(parent);
124156             if (!animWrap) {
124157                 animWrap = parent.animWrap = me.createAnimWrap(parent, index);
124158             }
124159             else if (animWrap.expanding) {
124160                 // If we collapse this node while it is still expanding then we
124161                 // have to remove the nodes from the animWrap.
124162                 animWrap.targetEl.select(this.itemSelector).remove();
124163             }
124164             animWrap.expanding = false;
124165             animWrap.collapsing = true;
124166         }
124167     },
124168     
124169     onCollapse: function(parent) {
124170         var me = this,
124171             queue = me.animQueue,
124172             id = parent.getId(),
124173             animWrap = me.getAnimWrap(parent),
124174             animateEl, targetEl;
124175
124176         if (!animWrap) {
124177             me.resetScrollers();
124178             return;
124179         }
124180         
124181         animateEl = animWrap.animateEl;
124182         targetEl = animWrap.targetEl;
124183
124184         queue[id] = true;
124185         
124186         // @TODO: we are setting it to 1 because quirks mode on IE seems to have issues with 0
124187         animateEl.stopAnimation();
124188         animateEl.slideOut('t', {
124189             duration: me.collapseDuration,
124190             listeners: {
124191                 scope: me,
124192                 lastframe: function() {
124193                     animWrap.el.remove();
124194                     delete animWrap.record.animWrap;
124195                     me.resetScrollers();
124196                     delete queue[id];
124197                 }             
124198             }
124199         });
124200         animWrap.isAnimating = true;
124201     },
124202     
124203     /**
124204      * Checks if a node is currently undergoing animation
124205      * @private
124206      * @param {Ext.data.Model} node The node
124207      * @return {Boolean} True if the node is animating
124208      */
124209     isAnimating: function(node) {
124210         return !!this.animQueue[node.getId()];    
124211     },
124212     
124213     collectData: function(records) {
124214         var data = this.callParent(arguments),
124215             rows = data.rows,
124216             len = rows.length,
124217             i = 0,
124218             row, record;
124219             
124220         for (; i < len; i++) {
124221             row = rows[i];
124222             record = records[i];
124223             if (record.get('qtip')) {
124224                 row.rowAttr = 'data-qtip="' + record.get('qtip') + '"';
124225                 if (record.get('qtitle')) {
124226                     row.rowAttr += ' ' + 'data-qtitle="' + record.get('qtitle') + '"';
124227                 }
124228             }
124229             if (record.isExpanded()) {
124230                 row.rowCls = (row.rowCls || '') + ' ' + this.expandedCls;
124231             }
124232             if (record.isLoading()) {
124233                 row.rowCls = (row.rowCls || '') + ' ' + this.loadingCls;
124234             }
124235         }
124236         
124237         return data;
124238     },
124239     
124240     /**
124241      * Expand a record that is loaded in the view.
124242      * @param {Ext.data.Model} record The record to expand
124243      * @param {Boolean} deep (optional) True to expand nodes all the way down the tree hierarchy.
124244      * @param {Function} callback (optional) The function to run after the expand is completed
124245      * @param {Object} scope (optional) The scope of the callback function.
124246      */
124247     expand: function(record, deep, callback, scope) {
124248         return record.expand(deep, callback, scope);
124249     },
124250     
124251     /**
124252      * Collapse a record that is loaded in the view.
124253      * @param {Ext.data.Model} record The record to collapse
124254      * @param {Boolean} deep (optional) True to collapse nodes all the way up the tree hierarchy.
124255      * @param {Function} callback (optional) The function to run after the collapse is completed
124256      * @param {Object} scope (optional) The scope of the callback function.
124257      */
124258     collapse: function(record, deep, callback, scope) {
124259         return record.collapse(deep, callback, scope);
124260     },
124261     
124262     /**
124263      * Toggle a record between expanded and collapsed.
124264      * @param {Ext.data.Record} recordInstance
124265      */
124266     toggle: function(record) {
124267         this[record.isExpanded() ? 'collapse' : 'expand'](record);
124268     },
124269     
124270     onItemDblClick: function(record, item, index) {
124271         this.callParent(arguments);
124272         if (this.toggleOnDblClick) {
124273             this.toggle(record);
124274         }
124275     },
124276     
124277     onBeforeItemMouseDown: function(record, item, index, e) {
124278         if (e.getTarget(this.expanderSelector, item)) {
124279             return false;
124280         }
124281         return this.callParent(arguments);
124282     },
124283     
124284     onItemClick: function(record, item, index, e) {
124285         if (e.getTarget(this.expanderSelector, item)) {
124286             this.toggle(record);
124287             return false;
124288         }
124289         return this.callParent(arguments);
124290     },
124291     
124292     onExpanderMouseOver: function(e, t) {
124293         e.getTarget(this.cellSelector, 10, true).addCls(this.expanderIconOverCls);
124294     },
124295     
124296     onExpanderMouseOut: function(e, t) {
124297         e.getTarget(this.cellSelector, 10, true).removeCls(this.expanderIconOverCls);
124298     },
124299     
124300     /**
124301      * Gets the base TreeStore from the bound TreePanel.
124302      */
124303     getTreeStore: function() {
124304         return this.panel.store;
124305     },    
124306     
124307     ensureSingleExpand: function(node) {
124308         var parent = node.parentNode;
124309         if (parent) {
124310             parent.eachChild(function(child) {
124311                 if (child !== node && child.isExpanded()) {
124312                     child.collapse();
124313                 }
124314             });
124315         }
124316     }
124317 });
124318 /**
124319  * @class Ext.tree.Panel
124320  * @extends Ext.panel.Table
124321  * 
124322  * The TreePanel provides tree-structured UI representation of tree-structured data.
124323  * A TreePanel must be bound to a {@link Ext.data.TreeStore}. TreePanel's support
124324  * multiple columns through the {@link columns} configuration. 
124325  * 
124326  * Simple TreePanel using inline data.
124327  *
124328  * {@img Ext.tree.Panel/Ext.tree.Panel1.png Ext.tree.Panel component}
124329  * 
124330  * ## Simple Tree Panel (no columns)
124331  *
124332  *     var store = Ext.create('Ext.data.TreeStore', {
124333  *         root: {
124334  *             expanded: true, 
124335  *             text:"",
124336  *             user:"",
124337  *             status:"", 
124338  *             children: [
124339  *                 { text:"detention", leaf: true },
124340  *                 { text:"homework", expanded: true, 
124341  *                     children: [
124342  *                         { text:"book report", leaf: true },
124343  *                         { text:"alegrbra", leaf: true}
124344  *                     ]
124345  *                 },
124346  *                 { text: "buy lottery tickets", leaf:true }
124347  *             ]
124348  *         }
124349  *     });     
124350  *             
124351  *     Ext.create('Ext.tree.Panel', {
124352  *         title: 'Simple Tree',
124353  *         width: 200,
124354  *         height: 150,
124355  *         store: store,
124356  *         rootVisible: false,        
124357  *         renderTo: Ext.getBody()
124358  *     });
124359  *
124360  * @xtype treepanel
124361  */
124362 Ext.define('Ext.tree.Panel', {
124363     extend: 'Ext.panel.Table',
124364     alias: 'widget.treepanel',
124365     alternateClassName: ['Ext.tree.TreePanel', 'Ext.TreePanel'],
124366     requires: ['Ext.tree.View', 'Ext.selection.TreeModel', 'Ext.tree.Column'],
124367     viewType: 'treeview',
124368     selType: 'treemodel',
124369     
124370     treeCls: Ext.baseCSSPrefix + 'tree-panel',
124371     
124372     /**
124373      * @cfg {Boolean} lines false to disable tree lines (defaults to true)
124374      */
124375     lines: true,
124376     
124377     /**
124378      * @cfg {Boolean} useArrows true to use Vista-style arrows in the tree (defaults to false)
124379      */
124380     useArrows: false,
124381     
124382     /**
124383      * @cfg {Boolean} singleExpand <tt>true</tt> if only 1 node per branch may be expanded
124384      */
124385     singleExpand: false,
124386     
124387     ddConfig: {
124388         enableDrag: true,
124389         enableDrop: true
124390     },
124391     
124392     /** 
124393      * @cfg {Boolean} animate <tt>true</tt> to enable animated expand/collapse (defaults to the value of {@link Ext#enableFx Ext.enableFx})
124394      */
124395             
124396     /** 
124397      * @cfg {Boolean} rootVisible <tt>false</tt> to hide the root node (defaults to <tt>true</tt>)
124398      */
124399     rootVisible: true,
124400     
124401     /** 
124402      * @cfg {Boolean} displayField The field inside the model that will be used as the node's text. (defaults to <tt>text</tt>)
124403      */    
124404     displayField: 'text',
124405
124406     /** 
124407      * @cfg {Boolean} root Allows you to not specify a store on this TreePanel. This is useful for creating a simple
124408      * tree with preloaded data without having to specify a TreeStore and Model. A store and model will be created and
124409      * root will be passed to that store.
124410      */
124411     root: null,
124412     
124413     // Required for the Lockable Mixin. These are the configurations which will be copied to the
124414     // normal and locked sub tablepanels
124415     normalCfgCopy: ['displayField', 'root', 'singleExpand', 'useArrows', 'lines', 'rootVisible', 'scroll'],
124416     lockedCfgCopy: ['displayField', 'root', 'singleExpand', 'useArrows', 'lines', 'rootVisible'],
124417
124418     /**
124419      * @cfg {Boolean} hideHeaders
124420      * Specify as <code>true</code> to hide the headers.
124421      */
124422     
124423     /**
124424      * @cfg {Boolean} folderSort Set to true to automatically prepend a leaf sorter to the store (defaults to <tt>undefined</tt>)
124425      */ 
124426     
124427     constructor: function(config) {
124428         config = config || {};
124429         if (config.animate === undefined) {
124430             config.animate = Ext.enableFx;
124431         }
124432         this.enableAnimations = config.animate;
124433         delete config.animate;
124434         
124435         this.callParent([config]);
124436     },
124437     
124438     initComponent: function() {
124439         var me = this,
124440             cls = [me.treeCls];
124441
124442         if (me.useArrows) {
124443             cls.push(Ext.baseCSSPrefix + 'tree-arrows');
124444             me.lines = false;
124445         }
124446         
124447         if (me.lines) {
124448             cls.push(Ext.baseCSSPrefix + 'tree-lines');
124449         } else if (!me.useArrows) {
124450             cls.push(Ext.baseCSSPrefix + 'tree-no-lines');
124451         }
124452
124453         if (!me.store || Ext.isObject(me.store) && !me.store.isStore) {
124454             me.store = Ext.create('Ext.data.TreeStore', Ext.apply({}, me.store || {}, {
124455                 root: me.root,
124456                 fields: me.fields,
124457                 model: me.model,
124458                 folderSort: me.folderSort
124459             }));
124460         }
124461         else if (me.root) {
124462             me.store = Ext.data.StoreManager.lookup(me.store);
124463             me.store.setRootNode(me.root);
124464             if (me.folderSort !== undefined) {
124465                 me.store.folderSort = me.folderSort;
124466                 me.store.sort();
124467             }            
124468         }
124469         
124470         // I'm not sure if we want to this. It might be confusing
124471         // if (me.initialConfig.rootVisible === undefined && !me.getRootNode()) {
124472         //     me.rootVisible = false;
124473         // }
124474         
124475         me.viewConfig = Ext.applyIf(me.viewConfig || {}, {
124476             rootVisible: me.rootVisible,
124477             animate: me.enableAnimations,
124478             singleExpand: me.singleExpand,
124479             node: me.store.getRootNode(),
124480             hideHeaders: me.hideHeaders
124481         });
124482         
124483         me.mon(me.store, {
124484             scope: me,
124485             rootchange: me.onRootChange,
124486             clear: me.onClear
124487         });
124488     
124489         me.relayEvents(me.store, [
124490             /**
124491              * @event beforeload
124492              * Event description
124493              * @param {Ext.data.Store} store This Store
124494              * @param {Ext.data.Operation} operation The Ext.data.Operation object that will be passed to the Proxy to load the Store
124495              */
124496             'beforeload',
124497
124498             /**
124499              * @event load
124500              * Fires whenever the store reads data from a remote data source.
124501              * @param {Ext.data.store} this
124502              * @param {Array} records An array of records
124503              * @param {Boolean} successful True if the operation was successful.
124504              */
124505             'load'   
124506         ]);
124507         
124508         me.store.on({
124509             /**
124510              * @event itemappend
124511              * Fires when a new child node is appended to a node in the tree.
124512              * @param {Tree} tree The owner tree
124513              * @param {Node} parent The parent node
124514              * @param {Node} node The newly appended node
124515              * @param {Number} index The index of the newly appended node
124516              */
124517             append: me.createRelayer('itemappend'),
124518             
124519             /**
124520              * @event itemremove
124521              * Fires when a child node is removed from a node in the tree
124522              * @param {Tree} tree The owner tree
124523              * @param {Node} parent The parent node
124524              * @param {Node} node The child node removed
124525              */
124526             remove: me.createRelayer('itemremove'),
124527             
124528             /**
124529              * @event itemmove
124530              * Fires when a node is moved to a new location in the tree
124531              * @param {Tree} tree The owner tree
124532              * @param {Node} node The node moved
124533              * @param {Node} oldParent The old parent of this node
124534              * @param {Node} newParent The new parent of this node
124535              * @param {Number} index The index it was moved to
124536              */
124537             move: me.createRelayer('itemmove'),
124538             
124539             /**
124540              * @event iteminsert
124541              * Fires when a new child node is inserted in a node in tree
124542              * @param {Tree} tree The owner tree
124543              * @param {Node} parent The parent node
124544              * @param {Node} node The child node inserted
124545              * @param {Node} refNode The child node the node was inserted before
124546              */
124547             insert: me.createRelayer('iteminsert'),
124548             
124549             /**
124550              * @event beforeitemappend
124551              * Fires before a new child is appended to a node in this tree, return false to cancel the append.
124552              * @param {Tree} tree The owner tree
124553              * @param {Node} parent The parent node
124554              * @param {Node} node The child node to be appended
124555              */
124556             beforeappend: me.createRelayer('beforeitemappend'),
124557             
124558             /**
124559              * @event beforeitemremove
124560              * Fires before a child is removed from a node in this tree, return false to cancel the remove.
124561              * @param {Tree} tree The owner tree
124562              * @param {Node} parent The parent node
124563              * @param {Node} node The child node to be removed
124564              */
124565             beforeremove: me.createRelayer('beforeitemremove'),
124566             
124567             /**
124568              * @event beforeitemmove
124569              * Fires before a node is moved to a new location in the tree. Return false to cancel the move.
124570              * @param {Tree} tree The owner tree
124571              * @param {Node} node The node being moved
124572              * @param {Node} oldParent The parent of the node
124573              * @param {Node} newParent The new parent the node is moving to
124574              * @param {Number} index The index it is being moved to
124575              */
124576             beforemove: me.createRelayer('beforeitemmove'),
124577             
124578             /**
124579              * @event beforeiteminsert
124580              * Fires before a new child is inserted in a node in this tree, return false to cancel the insert.
124581              * @param {Tree} tree The owner tree
124582              * @param {Node} parent The parent node
124583              * @param {Node} node The child node to be inserted
124584              * @param {Node} refNode The child node the node is being inserted before
124585              */
124586             beforeinsert: me.createRelayer('beforeiteminsert'),
124587              
124588             /**
124589              * @event itemexpand
124590              * Fires when a node is expanded.
124591              * @param {Node} this The expanding node
124592              */
124593             expand: me.createRelayer('itemexpand'),
124594              
124595             /**
124596              * @event itemcollapse
124597              * Fires when a node is collapsed.
124598              * @param {Node} this The collapsing node
124599              */
124600             collapse: me.createRelayer('itemcollapse'),
124601              
124602             /**
124603              * @event beforeitemexpand
124604              * Fires before a node is expanded.
124605              * @param {Node} this The expanding node
124606              */
124607             beforeexpand: me.createRelayer('beforeitemexpand'),
124608              
124609             /**
124610              * @event beforeitemcollapse
124611              * Fires before a node is collapsed.
124612              * @param {Node} this The collapsing node
124613              */
124614             beforecollapse: me.createRelayer('beforeitemcollapse')
124615         });
124616         
124617         // If the user specifies the headers collection manually then dont inject our own
124618         if (!me.columns) {
124619             if (me.initialConfig.hideHeaders === undefined) {
124620                 me.hideHeaders = true;
124621             }
124622             me.columns = [{
124623                 xtype    : 'treecolumn',
124624                 text     : 'Name',
124625                 flex     : 1,
124626                 dataIndex: me.displayField         
124627             }];
124628         }
124629         
124630         if (me.cls) {
124631             cls.push(me.cls);
124632         }
124633         me.cls = cls.join(' ');
124634         me.callParent();
124635         
124636         me.relayEvents(me.getView(), [
124637             /**
124638              * @event checkchange
124639              * Fires when a node with a checkbox's checked property changes
124640              * @param {Ext.data.Model} node The node who's checked property was changed
124641              * @param {Boolean} checked The node's new checked state
124642              */
124643             'checkchange'
124644         ]);
124645             
124646         // If the root is not visible and there is no rootnode defined, then just lets load the store
124647         if (!me.getView().rootVisible && !me.getRootNode()) {
124648             me.setRootNode({
124649                 expanded: true
124650             });
124651         }
124652     },
124653     
124654     onClear: function(){
124655         this.view.onClear();
124656     },
124657     
124658     setRootNode: function() {
124659         return this.store.setRootNode.apply(this.store, arguments);
124660     },
124661     
124662     getRootNode: function() {
124663         return this.store.getRootNode();
124664     },
124665     
124666     onRootChange: function(root) {
124667         this.view.setRootNode(root);
124668     },
124669
124670     /**
124671      * Retrieve an array of checked records.
124672      * @return {Array} An array containing the checked records
124673      */
124674     getChecked: function() {
124675         return this.getView().getChecked();
124676     },
124677     
124678     isItemChecked: function(rec) {
124679         return rec.get('checked');
124680     },
124681         
124682     /**
124683      * Expand all nodes
124684      * @param {Function} callback (optional) A function to execute when the expand finishes.
124685      * @param {Object} scope (optional) The scope of the callback function
124686      */
124687     expandAll : function(callback, scope) {
124688         var root = this.getRootNode();
124689         if (root) {
124690             root.expand(true, callback, scope);
124691         }
124692     },
124693
124694     /**
124695      * Collapse all nodes
124696      * @param {Function} callback (optional) A function to execute when the collapse finishes.
124697      * @param {Object} scope (optional) The scope of the callback function
124698      */
124699     collapseAll : function(callback, scope) {
124700         var root = this.getRootNode();
124701         if (root) {
124702             if (this.getView().rootVisible) {
124703                 root.collapse(true, callback, scope);
124704             }
124705             else {
124706                 root.collapseChildren(true, callback, scope);
124707             }
124708         }
124709     },
124710
124711     /**
124712      * Expand the tree to the path of a particular node.
124713      * @param {String} path The path to expand
124714      * @param {String} field (optional) The field to get the data from. Defaults to the model idProperty.
124715      * @param {String} separator (optional) A separator to use. Defaults to <tt>'/'</tt>.
124716      * @param {Function} callback (optional) A function to execute when the expand finishes. The callback will be called with
124717      * (success, lastNode) where success is if the expand was successful and lastNode is the last node that was expanded.
124718      * @param {Object} scope (optional) The scope of the callback function
124719      */
124720     expandPath: function(path, field, separator, callback, scope) {
124721         var me = this,
124722             current = me.getRootNode(),
124723             index = 1,
124724             view = me.getView(),
124725             keys,
124726             expander;
124727         
124728         field = field || me.getRootNode().idProperty;
124729         separator = separator || '/';
124730         
124731         if (Ext.isEmpty(path)) {
124732             Ext.callback(callback, scope || me, [false, null]);
124733             return;
124734         }
124735         
124736         keys = path.split(separator);
124737         if (current.get(field) != keys[1]) {
124738             // invalid root
124739             Ext.callback(callback, scope || me, [false, current]);
124740             return;
124741         }
124742         
124743         expander = function(){
124744             if (++index === keys.length) {
124745                 Ext.callback(callback, scope || me, [true, current]);
124746                 return;
124747             }
124748             var node = current.findChild(field, keys[index]);
124749             if (!node) {
124750                 Ext.callback(callback, scope || me, [false, current]);
124751                 return;
124752             }
124753             current = node;
124754             current.expand(false, expander);
124755         };
124756         current.expand(false, expander);
124757     },
124758     
124759     /**
124760      * Expand the tree to the path of a particular node, then selecti t.
124761      * @param {String} path The path to select
124762      * @param {String} field (optional) The field to get the data from. Defaults to the model idProperty.
124763      * @param {String} separator (optional) A separator to use. Defaults to <tt>'/'</tt>.
124764      * @param {Function} callback (optional) A function to execute when the select finishes. The callback will be called with
124765      * (bSuccess, oLastNode) where bSuccess is if the select was successful and oLastNode is the last node that was expanded.
124766      * @param {Object} scope (optional) The scope of the callback function
124767      */
124768     selectPath: function(path, field, separator, callback, scope) {
124769         var me = this,
124770             keys,
124771             last;
124772         
124773         field = field || me.getRootNode().idProperty;
124774         separator = separator || '/';
124775         
124776         keys = path.split(separator);
124777         last = keys.pop();
124778         
124779         me.expandPath(keys.join('/'), field, separator, function(success, node){
124780             var doSuccess = false;
124781             if (success && node) {
124782                 node = node.findChild(field, last);
124783                 if (node) {
124784                     me.getSelectionModel().select(node);
124785                     Ext.callback(callback, scope || me, [true, node]);
124786                     doSuccess = true;
124787                 }
124788             } else if (node === me.getRootNode()) {
124789                 doSuccess = true;
124790             }
124791             Ext.callback(callback, scope || me, [doSuccess, node]);
124792         }, me);
124793     }
124794 });
124795 /**
124796  * @class Ext.view.DragZone
124797  * @extends Ext.dd.DragZone
124798  * @private
124799  */
124800 Ext.define('Ext.view.DragZone', {
124801     extend: 'Ext.dd.DragZone',
124802     containerScroll: false,
124803
124804     constructor: function(config) {
124805         var me = this;
124806
124807         Ext.apply(me, config);
124808
124809         // Create a ddGroup unless one has been configured.
124810         // User configuration of ddGroups allows users to specify which
124811         // DD instances can interact with each other. Using one
124812         // based on the id of the View would isolate it and mean it can only
124813         // interact with a DropZone on the same View also using a generated ID.
124814         if (!me.ddGroup) {
124815             me.ddGroup = 'view-dd-zone-' + me.view.id;
124816         }
124817
124818         // Ext.dd.DragDrop instances are keyed by the ID of their encapsulating element.
124819         // So a View's DragZone cannot use the View's main element because the DropZone must use that
124820         // because the DropZone may need to scroll on hover at a scrolling boundary, and it is the View's
124821         // main element which handles scrolling.
124822         // We use the View's parent element to drag from. Ideally, we would use the internal structure, but that 
124823         // is transient; DataView's recreate the internal structure dynamically as data changes.
124824         // TODO: Ext 5.0 DragDrop must allow multiple DD objects to share the same element.
124825         me.callParent([me.view.el.dom.parentNode]);
124826
124827         me.ddel = Ext.get(document.createElement('div'));
124828         me.ddel.addCls(Ext.baseCSSPrefix + 'grid-dd-wrap');
124829     },
124830
124831     init: function(id, sGroup, config) {
124832         this.initTarget(id, sGroup, config);
124833         this.view.mon(this.view, {
124834             itemmousedown: this.onItemMouseDown,
124835             scope: this
124836         });
124837     },
124838
124839     onItemMouseDown: function(view, record, item, index, e) {
124840         if (!this.isPreventDrag(e, record, item, index)) {
124841             this.handleMouseDown(e);
124842         }
124843     },
124844
124845     // private template method
124846     isPreventDrag: function(e) {
124847         return false;
124848     },
124849
124850     getDragData: function(e) {
124851         var view = this.view,
124852             item = e.getTarget(view.getItemSelector()),
124853             record, selectionModel, records;
124854
124855         if (item) {
124856             record = view.getRecord(item);
124857             selectionModel = view.getSelectionModel();
124858             records = selectionModel.getSelection();
124859             return {
124860                 copy: this.view.copy || (this.view.allowCopy && e.ctrlKey),
124861                 event: new Ext.EventObjectImpl(e),
124862                 view: view,
124863                 ddel: this.ddel,
124864                 item: item,
124865                 records: records,
124866                 fromPosition: Ext.fly(item).getXY()
124867             };
124868         }
124869     },
124870
124871     onInitDrag: function(x, y) {
124872         var me = this,
124873             data = me.dragData,
124874             view = data.view,
124875             selectionModel = view.getSelectionModel(),
124876             record = view.getRecord(data.item),
124877             e = data.event;
124878
124879         // Update the selection to match what would have been selected if the user had
124880         // done a full click on the target node rather than starting a drag from it
124881         if (!selectionModel.isSelected(record) || e.hasModifier()) {
124882             selectionModel.selectWithEvent(record, e);
124883         }
124884         data.records = selectionModel.getSelection();
124885
124886         me.ddel.update(me.getDragText());
124887         me.proxy.update(me.ddel.dom);
124888         me.onStartDrag(x, y);
124889         return true;
124890     },
124891
124892     getDragText: function() {
124893         var count = this.dragData.records.length;
124894         return Ext.String.format(this.dragText, count, count == 1 ? '' : 's');
124895     },
124896
124897     getRepairXY : function(e, data){
124898         return data ? data.fromPosition : false;
124899     }
124900 });
124901 Ext.define('Ext.tree.ViewDragZone', {
124902     extend: 'Ext.view.DragZone',
124903
124904     isPreventDrag: function(e, record) {
124905         return (record.get('allowDrag') === false) || !!e.getTarget(this.view.expanderSelector);
124906     },
124907     
124908     afterRepair: function() {
124909         var me = this,
124910             view = me.view,
124911             selectedRowCls = view.selectedItemCls,
124912             records = me.dragData.records,
124913             fly = Ext.fly;
124914         
124915         if (Ext.enableFx && me.repairHighlight) {
124916             // Roll through all records and highlight all the ones we attempted to drag.
124917             Ext.Array.forEach(records, function(record) {
124918                 // anonymous fns below, don't hoist up unless below is wrapped in
124919                 // a self-executing function passing in item.
124920                 var item = view.getNode(record);
124921                 
124922                 // We must remove the selected row class before animating, because
124923                 // the selected row class declares !important on its background-color.
124924                 fly(item.firstChild).highlight(me.repairHighlightColor, {
124925                     listeners: {
124926                         beforeanimate: function() {
124927                             if (view.isSelected(item)) {
124928                                 fly(item).removeCls(selectedRowCls);
124929                             }
124930                         },
124931                         afteranimate: function() {
124932                             if (view.isSelected(item)) {
124933                                 fly(item).addCls(selectedRowCls);
124934                             }
124935                         }
124936                     }
124937                 });
124938             });
124939         }
124940         me.dragging = false;
124941     }
124942 });
124943 /**
124944  * @class Ext.tree.ViewDropZone
124945  * @extends Ext.view.DropZone
124946  * @private
124947  */
124948 Ext.define('Ext.tree.ViewDropZone', {
124949     extend: 'Ext.view.DropZone',
124950
124951     /**
124952      * @cfg {Boolean} allowParentInsert
124953      * Allow inserting a dragged node between an expanded parent node and its first child that will become a
124954      * sibling of the parent when dropped (defaults to false)
124955      */
124956     allowParentInserts: false,
124957  
124958     /**
124959      * @cfg {String} allowContainerDrop
124960      * True if drops on the tree container (outside of a specific tree node) are allowed (defaults to false)
124961      */
124962     allowContainerDrops: false,
124963
124964     /**
124965      * @cfg {String} appendOnly
124966      * True if the tree should only allow append drops (use for trees which are sorted, defaults to false)
124967      */
124968     appendOnly: false,
124969
124970     /**
124971      * @cfg {String} expandDelay
124972      * The delay in milliseconds to wait before expanding a target tree node while dragging a droppable node
124973      * over the target (defaults to 500)
124974      */
124975     expandDelay : 500,
124976
124977     indicatorCls: 'x-tree-ddindicator',
124978
124979     // private
124980     expandNode : function(node) {
124981         var view = this.view;
124982         if (!node.isLeaf() && !node.isExpanded()) {
124983             view.expand(node);
124984             this.expandProcId = false;
124985         }
124986     },
124987
124988     // private
124989     queueExpand : function(node) {
124990         this.expandProcId = Ext.Function.defer(this.expandNode, this.expandDelay, this, [node]);
124991     },
124992
124993     // private
124994     cancelExpand : function() {
124995         if (this.expandProcId) {
124996             clearTimeout(this.expandProcId);
124997             this.expandProcId = false;
124998         }
124999     },
125000
125001     getPosition: function(e, node) {
125002         var view = this.view,
125003             record = view.getRecord(node),
125004             y = e.getPageY(),
125005             noAppend = record.isLeaf(),
125006             noBelow = false,
125007             region = Ext.fly(node).getRegion(),
125008             fragment;
125009
125010         // If we are dragging on top of the root node of the tree, we always want to append.
125011         if (record.isRoot()) {
125012             return 'append';
125013         }
125014
125015         // Return 'append' if the node we are dragging on top of is not a leaf else return false.
125016         if (this.appendOnly) {
125017             return noAppend ? false : 'append';
125018         }
125019
125020         if (!this.allowParentInsert) {
125021             noBelow = record.hasChildNodes() && record.isExpanded();
125022         }
125023
125024         fragment = (region.bottom - region.top) / (noAppend ? 2 : 3);
125025         if (y >= region.top && y < (region.top + fragment)) {
125026             return 'before';
125027         }
125028         else if (!noBelow && (noAppend || (y >= (region.bottom - fragment) && y <= region.bottom))) {
125029             return 'after';
125030         }
125031         else {
125032             return 'append';
125033         }
125034     },
125035
125036     isValidDropPoint : function(node, position, dragZone, e, data) {
125037         if (!node || !data.item) {
125038             return false;
125039         }
125040
125041         var view = this.view,
125042             targetNode = view.getRecord(node),
125043             draggedRecords = data.records,
125044             dataLength = draggedRecords.length,
125045             ln = draggedRecords.length,
125046             i, record;
125047
125048         // No drop position, or dragged records: invalid drop point
125049         if (!(targetNode && position && dataLength)) {
125050             return false;
125051         }
125052
125053         // If the targetNode is within the folder we are dragging
125054         for (i = 0; i < ln; i++) {
125055             record = draggedRecords[i];
125056             if (record.isNode && record.contains(targetNode)) {
125057                 return false;
125058             }
125059         }
125060         
125061         // Respect the allowDrop field on Tree nodes
125062         if (position === 'append' && targetNode.get('allowDrop') == false) {
125063             return false;
125064         }
125065         else if (position != 'append' && targetNode.parentNode.get('allowDrop') == false) {
125066             return false;
125067         }
125068
125069         // If the target record is in the dragged dataset, then invalid drop
125070         if (Ext.Array.contains(draggedRecords, targetNode)) {
125071              return false;
125072         }
125073
125074         // @TODO: fire some event to notify that there is a valid drop possible for the node you're dragging
125075         // Yes: this.fireViewEvent(blah....) fires an event through the owning View.
125076         return true;
125077     },
125078
125079     onNodeOver : function(node, dragZone, e, data) {
125080         var position = this.getPosition(e, node),
125081             returnCls = this.dropNotAllowed,
125082             view = this.view,
125083             targetNode = view.getRecord(node),
125084             indicator = this.getIndicator(),
125085             indicatorX = 0,
125086             indicatorY = 0;
125087
125088         // auto node expand check
125089         this.cancelExpand();
125090         if (position == 'append' && !this.expandProcId && !Ext.Array.contains(data.records, targetNode) && !targetNode.isLeaf() && !targetNode.isExpanded()) {
125091             this.queueExpand(targetNode);
125092         }
125093             
125094         if (this.isValidDropPoint(node, position, dragZone, e, data)) {
125095             this.valid = true;
125096             this.currentPosition = position;
125097             this.overRecord = targetNode;
125098
125099             indicator.setWidth(Ext.fly(node).getWidth());
125100             indicatorY = Ext.fly(node).getY() - Ext.fly(view.el).getY() - 1;
125101
125102             if (position == 'before') {
125103                 returnCls = targetNode.isFirst() ? Ext.baseCSSPrefix + 'tree-drop-ok-above' : Ext.baseCSSPrefix + 'tree-drop-ok-between';
125104                 indicator.showAt(0, indicatorY);
125105                 indicator.toFront();
125106             }
125107             else if (position == 'after') {
125108                 returnCls = targetNode.isLast() ? Ext.baseCSSPrefix + 'tree-drop-ok-below' : Ext.baseCSSPrefix + 'tree-drop-ok-between';
125109                 indicatorY += Ext.fly(node).getHeight();
125110                 indicator.showAt(0, indicatorY);
125111                 indicator.toFront();
125112             }
125113             else {
125114                 returnCls = Ext.baseCSSPrefix + 'tree-drop-ok-append';
125115                 // @TODO: set a class on the parent folder node to be able to style it
125116                 indicator.hide();
125117             }
125118         }
125119         else {
125120             this.valid = false;
125121         }
125122
125123         this.currentCls = returnCls;
125124         return returnCls;
125125     },
125126
125127     onContainerOver : function(dd, e, data) {
125128         return e.getTarget('.' + this.indicatorCls) ? this.currentCls : this.dropNotAllowed;
125129     },
125130     
125131     notifyOut: function() {
125132         this.callParent(arguments);
125133         this.cancelExpand();
125134     },
125135
125136     handleNodeDrop : function(data, targetNode, position) {
125137         var me = this,
125138             view = me.view,
125139             parentNode = targetNode.parentNode,
125140             store = view.getStore(),
125141             recordDomNodes = [],
125142             records, i, len,
125143             insertionMethod, argList,
125144             needTargetExpand,
125145             transferData,
125146             processDrop;
125147
125148         // If the copy flag is set, create a copy of the Models with the same IDs
125149         if (data.copy) {
125150             records = data.records;
125151             data.records = [];
125152             for (i = 0, len = records.length; i < len; i++) {
125153                 data.records.push(Ext.apply({}, records[i].data));
125154             }
125155         }
125156
125157         // Cancel any pending expand operation
125158         me.cancelExpand();
125159
125160         // Grab a reference to the correct node insertion method.
125161         // Create an arg list array intended for the apply method of the
125162         // chosen node insertion method.
125163         // Ensure the target object for the method is referenced by 'targetNode'
125164         if (position == 'before') {
125165             insertionMethod = parentNode.insertBefore;
125166             argList = [null, targetNode];
125167             targetNode = parentNode;
125168         }
125169         else if (position == 'after') {
125170             if (targetNode.nextSibling) {
125171                 insertionMethod = parentNode.insertBefore;
125172                 argList = [null, targetNode.nextSibling];
125173             }
125174             else {
125175                 insertionMethod = parentNode.appendChild;
125176                 argList = [null];
125177             }
125178             targetNode = parentNode;
125179         }
125180         else {
125181             if (!targetNode.isExpanded()) {
125182                 needTargetExpand = true;
125183             }
125184             insertionMethod = targetNode.appendChild;
125185             argList = [null];
125186         }
125187
125188         // A function to transfer the data into the destination tree
125189         transferData = function() {
125190             var node;
125191             for (i = 0, len = data.records.length; i < len; i++) {
125192                 argList[0] = data.records[i];
125193                 node = insertionMethod.apply(targetNode, argList);
125194                 
125195                 if (Ext.enableFx && me.dropHighlight) {
125196                     recordDomNodes.push(view.getNode(node));
125197                 }
125198             }
125199             
125200             // Kick off highlights after everything's been inserted, so they are
125201             // more in sync without insertion/render overhead.
125202             if (Ext.enableFx && me.dropHighlight) {
125203                 //FIXME: the check for n.firstChild is not a great solution here. Ideally the line should simply read 
125204                 //Ext.fly(n.firstChild) but this yields errors in IE6 and 7. See ticket EXTJSIV-1705 for more details
125205                 Ext.Array.forEach(recordDomNodes, function(n) {
125206                     Ext.fly(n.firstChild ? n.firstChild : n).highlight(me.dropHighlightColor);
125207                 });
125208             }
125209         };
125210
125211         // If dropping right on an unexpanded node, transfer the data after it is expanded.
125212         if (needTargetExpand) {
125213             targetNode.expand(false, transferData);
125214         }
125215         // Otherwise, call the data transfer function immediately
125216         else {
125217             transferData();
125218         }
125219     }
125220 });
125221 /**
125222  * @class Ext.tree.ViewDDPlugin
125223  * @extends Ext.AbstractPlugin
125224  * <p>This plugin provides drag and/or drop functionality for a TreeView.</p>
125225  * <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}
125226  * and loads the data object which is passed to a cooperating {@link Ext.dd.DragZone DragZone}'s methods with the following properties:<ul>
125227  * <li>copy : Boolean
125228  *  <div class="sub-desc">The value of the TreeView's <code>copy</code> property, or <code>true</code> if the TreeView was configured
125229  *  with <code>allowCopy: true</code> <u>and</u> the control key was pressed when the drag operation was begun.</div></li>
125230  * <li>view : TreeView
125231  *  <div class="sub-desc">The source TreeView from which the drag originated.</div></li>
125232  * <li>ddel : HtmlElement
125233  *  <div class="sub-desc">The drag proxy element which moves with the mouse</div></li>
125234  * <li>item : HtmlElement
125235  *  <div class="sub-desc">The TreeView node upon which the mousedown event was registered.</div></li>
125236  * <li>records : Array
125237  *  <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>
125238  * </ul></p>
125239  * <p>It also creates a specialized instance of {@link Ext.dd.DropZone} which cooperates with other DropZones which are members of the same
125240  * ddGroup which processes such data objects.</p>
125241  * <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
125242  * <code>{@link #event-drop drop}</code></p>
125243  */
125244 Ext.define('Ext.tree.plugin.TreeViewDragDrop', {
125245     extend: 'Ext.AbstractPlugin',
125246     alias: 'plugin.treeviewdragdrop',
125247
125248     uses: [
125249         'Ext.tree.ViewDragZone',
125250         'Ext.tree.ViewDropZone'
125251     ],
125252
125253     /**
125254      * @event beforedrop
125255      * <p><b>This event is fired through the TreeView. Add listeners to the TreeView object</b></p>
125256      * <p>Fired when a drop gesture has been triggered by a mouseup event in a valid drop position in the TreeView.
125257      * @param {HtmlElement} node The TreeView node <b>if any</b> over which the mouse was positioned.</p>
125258      * <p>Returning <code>false</code> to this event signals that the drop gesture was invalid, and if the drag proxy
125259      * will animate back to the point from which the drag began.</p>
125260      * <p>Returning <code>0</code> To this event signals that the data transfer operation should not take place, but
125261      * that the gesture was valid, and that the repair operation should not take place.</p>
125262      * <p>Any other return value continues with the data transfer operation.</p>
125263      * @param {Object} data The data object gathered at mousedown time by the cooperating {@link Ext.dd.DragZone DragZone}'s
125264      * {@link Ext.dd.DragZone#getDragData getDragData} method it contains the following properties:<ul>
125265      * <li>copy : Boolean
125266      *  <div class="sub-desc">The value of the TreeView's <code>copy</code> property, or <code>true</code> if the TreeView was configured
125267      *  with <code>allowCopy: true</code> and the control key was pressed when the drag operation was begun</div></li>
125268      * <li>view : TreeView
125269      *  <div class="sub-desc">The source TreeView from which the drag originated.</div></li>
125270      * <li>ddel : HtmlElement
125271      *  <div class="sub-desc">The drag proxy element which moves with the mouse</div></li>
125272      * <li>item : HtmlElement
125273      *  <div class="sub-desc">The TreeView node upon which the mousedown event was registered.</div></li>
125274      * <li>records : Array
125275      *  <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>
125276      * </ul>
125277      * @param {Ext.data.Model} overModel The Model over which the drop gesture took place.
125278      * @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,
125279      * or the node is a branch node which accepts new child nodes.
125280      * @param {Function} dropFunction <p>A function to call to complete the data transfer operation and either move or copy Model instances from the source
125281      * View's Store to the destination View's Store.</p>
125282      * <p>This is useful when you want to perform some kind of asynchronous processing before confirming
125283      * the drop, such as an {@link Ext.window.MessageBox#confirm confirm} call, or an Ajax request.</p>
125284      * <p>Return <code>0</code> from this event handler, and call the <code>dropFunction</code> at any time to perform the data transfer.</p>
125285      */
125286
125287     /**
125288      * @event drop
125289      * <b>This event is fired through the TreeView. Add listeners to the TreeView object</b>
125290      * Fired when a drop operation has been completed and the data has been moved or copied.
125291      * @param {HtmlElement} node The TreeView node <b>if any</b> over which the mouse was positioned.
125292      * @param {Object} data The data object gathered at mousedown time by the cooperating {@link Ext.dd.DragZone DragZone}'s
125293      * {@link Ext.dd.DragZone#getDragData getDragData} method it contains the following properties:<ul>
125294      * <li>copy : Boolean
125295      *  <div class="sub-desc">The value of the TreeView's <code>copy</code> property, or <code>true</code> if the TreeView was configured
125296      *  with <code>allowCopy: true</code> and the control key was pressed when the drag operation was begun</div></li>
125297      * <li>view : TreeView
125298      *  <div class="sub-desc">The source TreeView from which the drag originated.</div></li>
125299      * <li>ddel : HtmlElement
125300      *  <div class="sub-desc">The drag proxy element which moves with the mouse</div></li>
125301      * <li>item : HtmlElement
125302      *  <div class="sub-desc">The TreeView node upon which the mousedown event was registered.</div></li>
125303      * <li>records : Array
125304      *  <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>
125305      * </ul>
125306      * @param {Ext.data.Model} overModel The Model over which the drop gesture took place.
125307      * @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,
125308      * or the node is a branch node which accepts new child nodes.
125309      */
125310
125311     dragText : '{0} selected node{1}',
125312
125313     /**
125314      * @cfg {Boolean} allowParentInsert
125315      * Allow inserting a dragged node between an expanded parent node and its first child that will become a
125316      * sibling of the parent when dropped (defaults to false)
125317      */
125318     allowParentInserts: false,
125319
125320     /**
125321      * @cfg {String} allowContainerDrop
125322      * True if drops on the tree container (outside of a specific tree node) are allowed (defaults to false)
125323      */
125324     allowContainerDrops: false,
125325
125326     /**
125327      * @cfg {String} appendOnly
125328      * True if the tree should only allow append drops (use for trees which are sorted, defaults to false)
125329      */
125330     appendOnly: false,
125331
125332     /**
125333      * @cfg {String} ddGroup
125334      * A named drag drop group to which this object belongs.  If a group is specified, then both the DragZones and DropZone
125335      * used by this plugin will only interact with other drag drop objects in the same group (defaults to 'TreeDD').
125336      */
125337     ddGroup : "TreeDD",
125338
125339     /**
125340      * @cfg {String} dragGroup
125341      * <p>The ddGroup to which the DragZone will belong.</p>
125342      * <p>This defines which other DropZones the DragZone will interact with. Drag/DropZones only interact with other Drag/DropZones
125343      * which are members of the same ddGroup.</p>
125344      */
125345
125346     /**
125347      * @cfg {String} dropGroup
125348      * <p>The ddGroup to which the DropZone will belong.</p>
125349      * <p>This defines which other DragZones the DropZone will interact with. Drag/DropZones only interact with other Drag/DropZones
125350      * which are members of the same ddGroup.</p>
125351      */
125352
125353     /**
125354      * @cfg {String} expandDelay
125355      * The delay in milliseconds to wait before expanding a target tree node while dragging a droppable node
125356      * over the target (defaults to 1000)
125357      */
125358     expandDelay : 1000,
125359
125360     /**
125361      * @cfg {Boolean} enableDrop
125362      * <p>Defaults to <code>true</code></p>
125363      * <p>Set to <code>false</code> to disallow the View from accepting drop gestures</p>
125364      */
125365     enableDrop: true,
125366
125367     /**
125368      * @cfg {Boolean} enableDrag
125369      * <p>Defaults to <code>true</code></p>
125370      * <p>Set to <code>false</code> to disallow dragging items from the View </p>
125371      */
125372     enableDrag: true,
125373     
125374     /**
125375      * @cfg {String} nodeHighlightColor The color to use when visually highlighting the dragged
125376      * or dropped node (defaults to 'c3daf9' - light blue). The color must be a 6 digit hex value, without
125377      * a preceding '#'. See also {@link #nodeHighlightOnDrop} and {@link #nodeHighlightOnRepair}.
125378      */
125379     nodeHighlightColor: 'c3daf9',
125380     
125381     /**
125382      * @cfg {Boolean} nodeHighlightOnDrop Whether or not to highlight any nodes after they are
125383      * successfully dropped on their target. Defaults to the value of `Ext.enableFx`.
125384      * See also {@link #nodeHighlightColor} and {@link #nodeHighlightOnRepair}.
125385      * @markdown
125386      */
125387     nodeHighlightOnDrop: Ext.enableFx,
125388     
125389     /**
125390      * @cfg {Boolean} nodeHighlightOnRepair Whether or not to highlight any nodes after they are
125391      * repaired from an unsuccessful drag/drop. Defaults to the value of `Ext.enableFx`.
125392      * See also {@link #nodeHighlightColor} and {@link #nodeHighlightOnDrop}.
125393      * @markdown
125394      */
125395     nodeHighlightOnRepair: Ext.enableFx,
125396
125397     init : function(view) {
125398         view.on('render', this.onViewRender, this, {single: true});
125399     },
125400
125401     /**
125402      * @private
125403      * AbstractComponent calls destroy on all its plugins at destroy time.
125404      */
125405     destroy: function() {
125406         Ext.destroy(this.dragZone, this.dropZone);
125407     },
125408
125409     onViewRender : function(view) {
125410         var me = this;
125411
125412         if (me.enableDrag) {
125413             me.dragZone = Ext.create('Ext.tree.ViewDragZone', {
125414                 view: view,
125415                 ddGroup: me.dragGroup || me.ddGroup,
125416                 dragText: me.dragText,
125417                 repairHighlightColor: me.nodeHighlightColor,
125418                 repairHighlight: me.nodeHighlightOnRepair
125419             });
125420         }
125421
125422         if (me.enableDrop) {
125423             me.dropZone = Ext.create('Ext.tree.ViewDropZone', {
125424                 view: view,
125425                 ddGroup: me.dropGroup || me.ddGroup,
125426                 allowContainerDrops: me.allowContainerDrops,
125427                 appendOnly: me.appendOnly,
125428                 allowParentInserts: me.allowParentInserts,
125429                 expandDelay: me.expandDelay,
125430                 dropHighlightColor: me.nodeHighlightColor,
125431                 dropHighlight: me.nodeHighlightOnDrop
125432             });
125433         }
125434     }
125435 });
125436 /**
125437  * @class Ext.util.Cookies
125438
125439 Utility class for setting/reading values from browser cookies.
125440 Values can be written using the {@link #set} method.
125441 Values can be read using the {@link #get} method.
125442 A cookie can be invalidated on the client machine using the {@link #clear} method.
125443
125444  * @markdown
125445  * @singleton
125446  */
125447 Ext.define('Ext.util.Cookies', {
125448     singleton: true,
125449     
125450     /**
125451      * Create a cookie with the specified name and value. Additional settings
125452      * for the cookie may be optionally specified (for example: expiration,
125453      * access restriction, SSL).
125454      * @param {String} name The name of the cookie to set. 
125455      * @param {Mixed} value The value to set for the cookie.
125456      * @param {Object} expires (Optional) Specify an expiration date the
125457      * cookie is to persist until.  Note that the specified Date object will
125458      * be converted to Greenwich Mean Time (GMT). 
125459      * @param {String} path (Optional) Setting a path on the cookie restricts
125460      * access to pages that match that path. Defaults to all pages (<tt>'/'</tt>). 
125461      * @param {String} domain (Optional) Setting a domain restricts access to
125462      * pages on a given domain (typically used to allow cookie access across
125463      * subdomains). For example, "sencha.com" will create a cookie that can be
125464      * accessed from any subdomain of sencha.com, including www.sencha.com,
125465      * support.sencha.com, etc.
125466      * @param {Boolean} secure (Optional) Specify true to indicate that the cookie
125467      * should only be accessible via SSL on a page using the HTTPS protocol.
125468      * Defaults to <tt>false</tt>. Note that this will only work if the page
125469      * calling this code uses the HTTPS protocol, otherwise the cookie will be
125470      * created with default options.
125471      */
125472     set : function(name, value){
125473         var argv = arguments,
125474             argc = arguments.length,
125475             expires = (argc > 2) ? argv[2] : null,
125476             path = (argc > 3) ? argv[3] : '/',
125477             domain = (argc > 4) ? argv[4] : null,
125478             secure = (argc > 5) ? argv[5] : false;
125479             
125480         document.cookie = name + "=" + escape(value) + ((expires === null) ? "" : ("; expires=" + expires.toGMTString())) + ((path === null) ? "" : ("; path=" + path)) + ((domain === null) ? "" : ("; domain=" + domain)) + ((secure === true) ? "; secure" : "");
125481     },
125482
125483     /**
125484      * Retrieves cookies that are accessible by the current page. If a cookie
125485      * does not exist, <code>get()</code> returns <tt>null</tt>.  The following
125486      * example retrieves the cookie called "valid" and stores the String value
125487      * in the variable <tt>validStatus</tt>.
125488      * <pre><code>
125489      * var validStatus = Ext.util.Cookies.get("valid");
125490      * </code></pre>
125491      * @param {String} name The name of the cookie to get
125492      * @return {Mixed} Returns the cookie value for the specified name;
125493      * null if the cookie name does not exist.
125494      */
125495     get : function(name){
125496         var arg = name + "=",
125497             alen = arg.length,
125498             clen = document.cookie.length,
125499             i = 0,
125500             j = 0;
125501             
125502         while(i < clen){
125503             j = i + alen;
125504             if(document.cookie.substring(i, j) == arg){
125505                 return this.getCookieVal(j);
125506             }
125507             i = document.cookie.indexOf(" ", i) + 1;
125508             if(i === 0){
125509                 break;
125510             }
125511         }
125512         return null;
125513     },
125514
125515     /**
125516      * Removes a cookie with the provided name from the browser
125517      * if found by setting its expiration date to sometime in the past. 
125518      * @param {String} name The name of the cookie to remove
125519      * @param {String} path (optional) The path for the cookie. This must be included if you included a path while setting the cookie.
125520      */
125521     clear : function(name, path){
125522         if(this.get(name)){
125523             path = path || '/';
125524             document.cookie = name + '=' + '; expires=Thu, 01-Jan-70 00:00:01 GMT; path=' + path;
125525         }
125526     },
125527     
125528     /**
125529      * @private
125530      */
125531     getCookieVal : function(offset){
125532         var endstr = document.cookie.indexOf(";", offset);
125533         if(endstr == -1){
125534             endstr = document.cookie.length;
125535         }
125536         return unescape(document.cookie.substring(offset, endstr));
125537     }
125538 });
125539
125540 /**
125541  * @class Ext.util.CSS
125542  * Utility class for manipulating CSS rules
125543  * @singleton
125544  */
125545 Ext.define('Ext.util.CSS', function() {
125546     var rules = null;
125547     var doc = document;
125548
125549     var camelRe = /(-[a-z])/gi;
125550     var camelFn = function(m, a){ return a.charAt(1).toUpperCase(); };
125551
125552     return {
125553
125554         singleton: true,
125555
125556         constructor: function() {
125557             this.rules = {};
125558             this.initialized = false;
125559         },
125560  
125561         /**
125562          * Creates a stylesheet from a text blob of rules.
125563          * These rules will be wrapped in a STYLE tag and appended to the HEAD of the document.
125564          * @param {String} cssText The text containing the css rules
125565          * @param {String} id An id to add to the stylesheet for later removal
125566          * @return {StyleSheet}
125567          */
125568         createStyleSheet : function(cssText, id) {
125569             var ss,
125570                 head = doc.getElementsByTagName("head")[0],
125571                 styleEl = doc.createElement("style");
125572
125573             styleEl.setAttribute("type", "text/css");
125574             if (id) {
125575                styleEl.setAttribute("id", id);
125576             }
125577
125578             if (Ext.isIE) {
125579                head.appendChild(styleEl);
125580                ss = styleEl.styleSheet;
125581                ss.cssText = cssText;
125582             } else {
125583                 try{
125584                     styleEl.appendChild(doc.createTextNode(cssText));
125585                 } catch(e) {
125586                    styleEl.cssText = cssText;
125587                 }
125588                 head.appendChild(styleEl);
125589                 ss = styleEl.styleSheet ? styleEl.styleSheet : (styleEl.sheet || doc.styleSheets[doc.styleSheets.length-1]);
125590             }
125591             this.cacheStyleSheet(ss);
125592             return ss;
125593         },
125594
125595         /**
125596          * Removes a style or link tag by id
125597          * @param {String} id The id of the tag
125598          */
125599         removeStyleSheet : function(id) {
125600             var existing = document.getElementById(id);
125601             if (existing) {
125602                 existing.parentNode.removeChild(existing);
125603             }
125604         },
125605
125606         /**
125607          * Dynamically swaps an existing stylesheet reference for a new one
125608          * @param {String} id The id of an existing link tag to remove
125609          * @param {String} url The href of the new stylesheet to include
125610          */
125611         swapStyleSheet : function(id, url) {
125612             var doc = document;
125613             this.removeStyleSheet(id);
125614             var ss = doc.createElement("link");
125615             ss.setAttribute("rel", "stylesheet");
125616             ss.setAttribute("type", "text/css");
125617             ss.setAttribute("id", id);
125618             ss.setAttribute("href", url);
125619             doc.getElementsByTagName("head")[0].appendChild(ss);
125620         },
125621
125622         /**
125623          * Refresh the rule cache if you have dynamically added stylesheets
125624          * @return {Object} An object (hash) of rules indexed by selector
125625          */
125626         refreshCache : function() {
125627             return this.getRules(true);
125628         },
125629
125630         // private
125631         cacheStyleSheet : function(ss) {
125632             if(!rules){
125633                 rules = {};
125634             }
125635             try {// try catch for cross domain access issue
125636                 var ssRules = ss.cssRules || ss.rules,
125637                     selectorText,
125638                     i = ssRules.length - 1,
125639                     j,
125640                     selectors;
125641
125642                 for (; i >= 0; --i) {
125643                     selectorText = ssRules[i].selectorText;
125644                     if (selectorText) {
125645  
125646                         // Split in case there are multiple, comma-delimited selectors
125647                         selectorText = selectorText.split(',');
125648                         selectors = selectorText.length;
125649                         for (j = 0; j < selectors; j++) {
125650                             rules[Ext.String.trim(selectorText[j]).toLowerCase()] = ssRules[i];
125651                         }
125652                     }
125653                 }
125654             } catch(e) {}
125655         },
125656
125657         /**
125658         * Gets all css rules for the document
125659         * @param {Boolean} refreshCache true to refresh the internal cache
125660         * @return {Object} An object (hash) of rules indexed by selector
125661         */
125662         getRules : function(refreshCache) {
125663             if (rules === null || refreshCache) {
125664                 rules = {};
125665                 var ds = doc.styleSheets,
125666                     i = 0,
125667                     len = ds.length;
125668
125669                 for (; i < len; i++) {
125670                     try {
125671                         if (!ds[i].disabled) {
125672                             this.cacheStyleSheet(ds[i]);
125673                         }
125674                     } catch(e) {} 
125675                 }
125676             }
125677             return rules;
125678         },
125679
125680         /**
125681          * Gets an an individual CSS rule by selector(s)
125682          * @param {String/Array} selector The CSS selector or an array of selectors to try. The first selector that is found is returned.
125683          * @param {Boolean} refreshCache true to refresh the internal cache if you have recently updated any rules or added styles dynamically
125684          * @return {CSSRule} The CSS rule or null if one is not found
125685          */
125686         getRule: function(selector, refreshCache) {
125687             var rs = this.getRules(refreshCache);
125688             if (!Ext.isArray(selector)) {
125689                 return rs[selector.toLowerCase()];
125690             }
125691             for (var i = 0; i < selector.length; i++) {
125692                 if (rs[selector[i]]) {
125693                     return rs[selector[i].toLowerCase()];
125694                 }
125695             }
125696             return null;
125697         },
125698
125699         /**
125700          * Updates a rule property
125701          * @param {String/Array} selector If it's an array it tries each selector until it finds one. Stops immediately once one is found.
125702          * @param {String} property The css property
125703          * @param {String} value The new value for the property
125704          * @return {Boolean} true If a rule was found and updated
125705          */
125706         updateRule : function(selector, property, value){
125707             if (!Ext.isArray(selector)) {
125708                 var rule = this.getRule(selector);
125709                 if (rule) {
125710                     rule.style[property.replace(camelRe, camelFn)] = value;
125711                     return true;
125712                 }
125713             } else {
125714                 for (var i = 0; i < selector.length; i++) {
125715                     if (this.updateRule(selector[i], property, value)) {
125716                         return true;
125717                     }
125718                 }
125719             }
125720             return false;
125721         }
125722     };
125723 }());
125724 /**
125725  * @class Ext.util.History
125726  * History management component that allows you to register arbitrary tokens that signify application
125727  * history state on navigation actions.  You can then handle the history {@link #change} event in order
125728  * to reset your application UI to the appropriate state when the user navigates forward or backward through
125729  * the browser history stack.
125730  * @singleton
125731  */
125732 Ext.define('Ext.util.History', {
125733     singleton: true,
125734     alternateClassName: 'Ext.History',
125735     mixins: {
125736         observable: 'Ext.util.Observable'
125737     },
125738     
125739     constructor: function() {
125740         var me = this;
125741         me.oldIEMode = Ext.isIE6 || Ext.isIE7 || !Ext.isStrict && Ext.isIE8;
125742         me.iframe = null;
125743         me.hiddenField = null;
125744         me.ready = false;
125745         me.currentToken = null;
125746     },
125747     
125748     getHash: function() {
125749         var href = window.location.href,
125750             i = href.indexOf("#");
125751             
125752         return i >= 0 ? href.substr(i + 1) : null;
125753     },
125754
125755     doSave: function() {
125756         this.hiddenField.value = this.currentToken;
125757     },
125758     
125759
125760     handleStateChange: function(token) {
125761         this.currentToken = token;
125762         this.fireEvent('change', token);
125763     },
125764
125765     updateIFrame: function(token) {
125766         var html = '<html><body><div id="state">' + 
125767                     Ext.util.Format.htmlEncode(token) + 
125768                     '</div></body></html>';
125769
125770         try {
125771             var doc = this.iframe.contentWindow.document;
125772             doc.open();
125773             doc.write(html);
125774             doc.close();
125775             return true;
125776         } catch (e) {
125777             return false;
125778         }
125779     },
125780
125781     checkIFrame: function () {
125782         var me = this,
125783             contentWindow = me.iframe.contentWindow;
125784             
125785         if (!contentWindow || !contentWindow.document) {
125786             Ext.Function.defer(this.checkIFrame, 10, this);
125787             return;
125788         }
125789        
125790         var doc = contentWindow.document,
125791             elem = doc.getElementById("state"),
125792             oldToken = elem ? elem.innerText : null,
125793             oldHash = me.getHash();
125794            
125795         Ext.TaskManager.start({
125796             run: function () {
125797                 var doc = contentWindow.document,
125798                     elem = doc.getElementById("state"),
125799                     newToken = elem ? elem.innerText : null,
125800                     newHash = me.getHash();
125801
125802                 if (newToken !== oldToken) {
125803                     oldToken = newToken;
125804                     me.handleStateChange(newToken);
125805                     window.top.location.hash = newToken;
125806                     oldHash = newToken;
125807                     me.doSave();
125808                 } else if (newHash !== oldHash) {
125809                     oldHash = newHash;
125810                     me.updateIFrame(newHash);
125811                 }
125812             }, 
125813             interval: 50,
125814             scope: me
125815         });
125816         me.ready = true;
125817         me.fireEvent('ready', me);            
125818     },
125819
125820     startUp: function () {
125821         var me = this;
125822         
125823         me.currentToken = me.hiddenField.value || this.getHash();
125824
125825         if (me.oldIEMode) {
125826             me.checkIFrame();
125827         } else {
125828             var hash = me.getHash();
125829             Ext.TaskManager.start({
125830                 run: function () {
125831                     var newHash = me.getHash();
125832                     if (newHash !== hash) {
125833                         hash = newHash;
125834                         me.handleStateChange(hash);
125835                         me.doSave();
125836                     }
125837                 },
125838                 interval: 50,
125839                 scope: me
125840             });
125841             me.ready = true;
125842             me.fireEvent('ready', me);
125843         }
125844         
125845     },
125846
125847     /**
125848      * The id of the hidden field required for storing the current history token.
125849      * @type String
125850      * @property
125851      */
125852     fieldId: Ext.baseCSSPrefix + 'history-field',
125853     /**
125854      * The id of the iframe required by IE to manage the history stack.
125855      * @type String
125856      * @property
125857      */
125858     iframeId: Ext.baseCSSPrefix + 'history-frame',
125859
125860     /**
125861      * Initialize the global History instance.
125862      * @param {Boolean} onReady (optional) A callback function that will be called once the history
125863      * component is fully initialized.
125864      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the callback is executed. Defaults to the browser window.
125865      */
125866     init: function (onReady, scope) {
125867         var me = this;
125868         
125869         if (me.ready) {
125870             Ext.callback(onReady, scope, [me]);
125871             return;
125872         }
125873         
125874         if (!Ext.isReady) {
125875             Ext.onReady(function() {
125876                 me.init(onReady, scope);
125877             });
125878             return;
125879         }
125880         
125881         me.hiddenField = Ext.getDom(me.fieldId);
125882         
125883         if (me.oldIEMode) {
125884             me.iframe = Ext.getDom(me.iframeId);
125885         }
125886         
125887         me.addEvents(
125888             /**
125889              * @event ready
125890              * Fires when the Ext.util.History singleton has been initialized and is ready for use.
125891              * @param {Ext.util.History} The Ext.util.History singleton.
125892              */
125893             'ready',
125894             /**
125895              * @event change
125896              * Fires when navigation back or forwards within the local page's history occurs.
125897              * @param {String} token An identifier associated with the page state at that point in its history.
125898              */
125899             'change'
125900         );
125901         
125902         if (onReady) {
125903             me.on('ready', onReady, scope, {single: true});
125904         }
125905         me.startUp();
125906     },
125907
125908     /**
125909      * Add a new token to the history stack. This can be any arbitrary value, although it would
125910      * commonly be the concatenation of a component id and another id marking the specifc history
125911      * state of that component.  Example usage:
125912      * <pre><code>
125913 // Handle tab changes on a TabPanel
125914 tabPanel.on('tabchange', function(tabPanel, tab){
125915 Ext.History.add(tabPanel.id + ':' + tab.id);
125916 });
125917 </code></pre>
125918      * @param {String} token The value that defines a particular application-specific history state
125919      * @param {Boolean} preventDuplicates When true, if the passed token matches the current token
125920      * it will not save a new history step. Set to false if the same state can be saved more than once
125921      * at the same history stack location (defaults to true).
125922      */
125923     add: function (token, preventDup) {
125924         var me = this;
125925         
125926         if (preventDup !== false) {
125927             if (me.getToken() === token) {
125928                 return true;
125929             }
125930         }
125931         
125932         if (me.oldIEMode) {
125933             return me.updateIFrame(token);
125934         } else {
125935             window.top.location.hash = token;
125936             return true;
125937         }
125938     },
125939
125940     /**
125941      * Programmatically steps back one step in browser history (equivalent to the user pressing the Back button).
125942      */
125943     back: function() {
125944         window.history.go(-1);
125945     },
125946
125947     /**
125948      * Programmatically steps forward one step in browser history (equivalent to the user pressing the Forward button).
125949      */
125950     forward: function(){
125951         window.history.go(1);
125952     },
125953
125954     /**
125955      * Retrieves the currently-active history token.
125956      * @return {String} The token
125957      */
125958     getToken: function() {
125959         return this.ready ? this.currentToken : this.getHash();
125960     }
125961 });
125962 /**
125963  * @class Ext.view.TableChunker
125964  * 
125965  * Produces optimized XTemplates for chunks of tables to be
125966  * used in grids, trees and other table based widgets.
125967  *
125968  * @singleton
125969  */
125970 Ext.define('Ext.view.TableChunker', {
125971     singleton: true,
125972     requires: ['Ext.XTemplate'],
125973     metaTableTpl: [
125974         '{[this.openTableWrap()]}',
125975         '<table class="' + Ext.baseCSSPrefix + 'grid-table ' + Ext.baseCSSPrefix + 'grid-table-resizer" border="0" cellspacing="0" cellpadding="0" {[this.embedFullWidth()]}>',
125976             '<tbody>',
125977             '<tr>',
125978             '<tpl for="columns">',
125979                 '<th class="' + Ext.baseCSSPrefix + 'grid-col-resizer-{id}" style="width: {width}px; height: 0px;"></th>',
125980             '</tpl>',
125981             '</tr>',
125982             '{[this.openRows()]}',
125983                 '{row}',
125984                 '<tpl for="features">',
125985                     '{[this.embedFeature(values, parent, xindex, xcount)]}',
125986                 '</tpl>',
125987             '{[this.closeRows()]}',
125988             '</tbody>',
125989         '</table>',
125990         '{[this.closeTableWrap()]}'
125991     ],
125992
125993     constructor: function() {
125994         Ext.XTemplate.prototype.recurse = function(values, reference) {
125995             return this.apply(reference ? values[reference] : values);
125996         };
125997     },
125998
125999     embedFeature: function(values, parent, x, xcount) {
126000         var tpl = '';
126001         if (!values.disabled) {
126002             tpl = values.getFeatureTpl(values, parent, x, xcount);
126003         }
126004         return tpl;
126005     },
126006
126007     embedFullWidth: function() {
126008         return 'style="width: {fullWidth}px;"';
126009     },
126010
126011     openRows: function() {
126012         return '<tpl for="rows">';
126013     },
126014
126015     closeRows: function() {
126016         return '</tpl>';
126017     },
126018
126019     metaRowTpl: [
126020         '<tr class="' + Ext.baseCSSPrefix + 'grid-row {addlSelector} {[this.embedRowCls()]}" {[this.embedRowAttr()]}>',
126021             '<tpl for="columns">',
126022                 '<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>',
126023             '</tpl>',
126024         '</tr>'
126025     ],
126026     
126027     firstOrLastCls: function(xindex, xcount) {
126028         var cssCls = '';
126029         if (xindex === 1) {
126030             cssCls = Ext.baseCSSPrefix + 'grid-cell-first';
126031         } else if (xindex === xcount) {
126032             cssCls = Ext.baseCSSPrefix + 'grid-cell-last';
126033         }
126034         return cssCls;
126035     },
126036     
126037     embedRowCls: function() {
126038         return '{rowCls}';
126039     },
126040     
126041     embedRowAttr: function() {
126042         return '{rowAttr}';
126043     },
126044     
126045     openTableWrap: function() {
126046         return '';
126047     },
126048     
126049     closeTableWrap: function() {
126050         return '';
126051     },
126052
126053     getTableTpl: function(cfg, textOnly) {
126054         var tpl,
126055             tableTplMemberFns = {
126056                 openRows: this.openRows,
126057                 closeRows: this.closeRows,
126058                 embedFeature: this.embedFeature,
126059                 embedFullWidth: this.embedFullWidth,
126060                 openTableWrap: this.openTableWrap,
126061                 closeTableWrap: this.closeTableWrap
126062             },
126063             tplMemberFns = {},
126064             features = cfg.features || [],
126065             ln = features.length,
126066             i  = 0,
126067             memberFns = {
126068                 embedRowCls: this.embedRowCls,
126069                 embedRowAttr: this.embedRowAttr,
126070                 firstOrLastCls: this.firstOrLastCls
126071             },
126072             // copy the default
126073             metaRowTpl = Array.prototype.slice.call(this.metaRowTpl, 0),
126074             metaTableTpl;
126075             
126076         for (; i < ln; i++) {
126077             if (!features[i].disabled) {
126078                 features[i].mutateMetaRowTpl(metaRowTpl);
126079                 Ext.apply(memberFns, features[i].getMetaRowTplFragments());
126080                 Ext.apply(tplMemberFns, features[i].getFragmentTpl());
126081                 Ext.apply(tableTplMemberFns, features[i].getTableFragments());
126082             }
126083         }
126084         
126085         metaRowTpl = Ext.create('Ext.XTemplate', metaRowTpl.join(''), memberFns);
126086         cfg.row = metaRowTpl.applyTemplate(cfg);
126087         
126088         metaTableTpl = Ext.create('Ext.XTemplate', this.metaTableTpl.join(''), tableTplMemberFns);
126089         
126090         tpl = metaTableTpl.applyTemplate(cfg);
126091         
126092         // TODO: Investigate eliminating.
126093         if (!textOnly) {
126094             tpl = Ext.create('Ext.XTemplate', tpl, tplMemberFns);
126095         }
126096         return tpl;
126097         
126098     }
126099 });
126100
126101
126102
126103